@starci/hfs 5.1.0 → 5.3.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 +54 -0
- package/README.md +11 -2
- package/cache/hfs-family.d.mts +2 -3
- package/cache/hfs-family.mjs +4 -9
- package/cache/secret-records.d.mts +7 -0
- package/cache/secret-records.mjs +12 -0
- package/cache/session.mjs +4 -2
- package/codemod/account-doors.d.mts +36 -0
- package/codemod/account-doors.mjs +128 -0
- package/codemod/main.mjs +31 -1
- package/codemod/module-definition.d.mts +52 -0
- package/codemod/module-definition.mjs +242 -0
- package/codemod/quality-binding.d.mts +33 -0
- package/codemod/quality-binding.mjs +38 -0
- package/codemod/secrets-cache.d.mts +35 -0
- package/codemod/secrets-cache.mjs +70 -0
- package/codemod/sops-rules.d.mts +34 -0
- package/codemod/sops-rules.mjs +69 -0
- package/codemod/theme-tokens.mjs +48 -2
- package/db/postgres.mjs +9 -1
- package/lint/run.d.mts +6 -0
- package/lint/run.mjs +63 -10
- package/package.json +1 -1
- package/runtime/knowledge/hfs/canon-pins.yaml +65 -11
- package/runtime/knowledge/hfs/fe-stack.yaml +9 -9
- package/runtime/knowledge/hfs/fork-images.yaml +7 -0
- package/runtime/knowledge/hfs/rules.yaml +67 -15
- package/runtime/knowledge/hfs/slots.yaml +174 -30
- package/runtime/modules/kernel/failure-codes.yaml +67 -4
- package/runtime/modules/schemas/application-stacks.schema.yaml +6 -0
- package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
- package/runtime/scripts/hfs/architecture/config.mjs +25 -1
- package/runtime/scripts/hfs/architecture/identity.mjs +18 -83
- package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
- package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
- package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
- 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/architecture/type-context.d.mts +4 -1
- package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
- package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
- package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
- package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
- 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/database-access.mjs +39 -1
- package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
- package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
- package/runtime/scripts/hfs/rules/secrets.mjs +11 -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-errors.d.mts +3 -0
- package/runtime/scripts/hfs/secret-errors.mjs +8 -0
- package/runtime/scripts/hfs/secret-init.d.mts +6 -0
- package/runtime/scripts/hfs/secret-init.mjs +23 -0
- package/runtime/scripts/hfs/secret-sops.mjs +9 -1
- package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
- package/runtime/scripts/hfs/secret-standard.mjs +106 -0
- package/runtime/scripts/hfs/secret.mjs +15 -8
- 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/hfs/view.d.mts +23 -0
- package/runtime/scripts/hfs/view.mjs +68 -0
- package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
- package/runtime/scripts/lib/declaration-surface.mjs +55 -1
- package/runtime/scripts/lib/secret-records.d.mts +13 -0
- package/runtime/scripts/lib/secret-records.mjs +83 -0
- package/runtime/scripts/lib/swarm-stack.d.mts +2 -1
- package/runtime/scripts/lib/swarm-stack.mjs +2 -2
- package/scaffold/add-inbox-lite.mjs +2 -2
- package/scaffold/add-module.d.mts +16 -0
- package/scaffold/add-module.mjs +232 -0
- package/scaffold/add-queue-lite.mjs +50 -3
- package/scaffold/add.mjs +5 -0
- package/scaffold/app.d.mts +2 -2
- package/scaffold/app.mjs +16 -18
- package/scaffold/lite-exports.mjs +1 -15
- package/scaffold/module-registry.d.mts +19 -0
- package/scaffold/module-registry.mjs +84 -0
- package/scripts/sync-bundles.d.mts +8 -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/images/openclaw-runtime/Dockerfile +18 -0
- package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
- package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
- package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -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 +169 -0
- package/stack/local-verify.d.mts +6 -0
- package/stack/local-verify.mjs +21 -0
- package/stack/runtime-layers.d.mts +21 -0
- package/stack/runtime-layers.mjs +67 -0
- package/sync/hygiene.mjs +3 -1
- package/sync/index.d.mts +15 -12
- package/sync/index.mjs +114 -28
- package/templates/app/auth-doors.sql.partial +115 -0
- package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
- 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/core.json +1 -1
- 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 +85 -70
- package/templates/app/skeleton-lite/.starcistacks/dev/README.md +24 -12
- package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +43 -0
- package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +21 -26
- package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +6 -0
- package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +6 -1
- package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +76 -13
- package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +44 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -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/resend.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -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/application/signup.command.ts +11 -0
- package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +6 -0
- package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +21 -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 +6 -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 +145 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -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/features/api/auth/transport/http/dto/resend.request.ts +16 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
- package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -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 +19 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +209 -0
- package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +84 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
- package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
- package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -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-email-token.entity.ts +34 -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 +10 -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 +95 -0
- package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +93 -0
- package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +40 -0
- package/templates/be/inbox/module-definition.ts.tpl +2 -9
- package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
- package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
- package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
- package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
- package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
- package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
- package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
- package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
- package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
- package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
- package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
- package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
- package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
- package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
- package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
- package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
- 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 +12 -5
- package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +40 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.decorators.ts +22 -1
- package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +50 -2
- package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +3 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
- package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +28 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
- package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -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 +5 -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/entities.ts +2 -1
- package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/http-security/index.ts +1 -1
- package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
- package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +22 -4
- package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
- package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
- package/templates/be/table/module-definition.ts.tpl +2 -9
- 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 -4
- package/templates/fe/auth-lite/apps/web/src/modules/auth/client.ts +26 -0
- package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
- package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
- 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/useViewerId.ts +11 -0
- package/templates/fe/skeleton/apps/__app__/src/modules/toast/toast.tsx +15 -12
- package/templates/fe/skeleton/apps/__app__/src/modules/viewer-cache/index.ts +5 -0
- package/templates/fe/skeleton/apps/__app__/src/redux/slices/session.ts +1 -1
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
- package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
- package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
- 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]/auth/confirm/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
- 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/pages/AppConfirmPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
- package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
- package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
- package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
- package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
- 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 -41
- package/templates/be/skeleton-lite/src/modules/integrations/passport/strategies/local.strategy.ts +0 -40
- 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
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCapabilityModule } from "@starci/nest-kit"
|
|
2
2
|
import type { LoggingOptions } from "./logging.options"
|
|
3
3
|
|
|
4
|
-
const built = new ConfigurableModuleBuilder<LoggingOptions>()
|
|
5
|
-
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
|
-
.build()
|
|
7
|
-
type Host = ConfigurableModuleHost<LoggingOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
-
|
|
9
4
|
/** The configurable-module base of the logging capability; `isGlobal` is decided by the app root. */
|
|
10
|
-
export const
|
|
11
|
-
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
-
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
5
|
+
export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<LoggingOptions>()
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineCapabilityModule } from "@starci/nest-kit"
|
|
2
2
|
import type { RetryOptions } from "./retry.options"
|
|
3
3
|
|
|
4
|
-
const built = new ConfigurableModuleBuilder<RetryOptions>()
|
|
5
|
-
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
|
-
.build()
|
|
7
|
-
type Host = ConfigurableModuleHost<RetryOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
-
|
|
9
4
|
/** The configurable-module base of the retry capability; the app root decides its global scope. */
|
|
10
|
-
export const
|
|
11
|
-
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
-
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
5
|
+
export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<RetryOptions>()
|
|
@@ -4,6 +4,7 @@ import { parsePrimaryDatabaseConfig } from "@modules/platform/database"
|
|
|
4
4
|
import { parseHttpConfig } from "@modules/platform/http"
|
|
5
5
|
import { parseHttpSecurityConfig } from "@modules/platform/http-security"
|
|
6
6
|
import { parsePassportConfig } from "@modules/integrations/passport"
|
|
7
|
+
import { parseMailConfig } from "@modules/integrations/mail"
|
|
7
8
|
import { parseObjectStorageConfig } from "@modules/integrations/object-storage"
|
|
8
9
|
|
|
9
10
|
/** The API environment values required before composition. */
|
|
@@ -20,6 +21,8 @@ export interface ApiEnv {
|
|
|
20
21
|
readonly passport: ReturnType<typeof parsePassportConfig>
|
|
21
22
|
/** S3-compatible object storage. */
|
|
22
23
|
readonly objectStorage: ReturnType<typeof parseObjectStorageConfig>
|
|
24
|
+
/** Outgoing mail: the SMTP server and the app's own mail settings. */
|
|
25
|
+
readonly mail: ReturnType<typeof parseMailConfig>
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
/** Reads the API environment; missing or invalid values fail boot, and secrets use `_FILE` first. */
|
|
@@ -30,4 +33,5 @@ export const readApiEnv = (env: EnvSource): ApiEnv => ({
|
|
|
30
33
|
database: parsePrimaryDatabaseConfig(env),
|
|
31
34
|
passport: parsePassportConfig(env),
|
|
32
35
|
objectStorage: parseObjectStorageConfig(env),
|
|
36
|
+
mail: parseMailConfig(env),
|
|
33
37
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PassportOptions } from "@modules/integrations/passport"
|
|
2
|
+
import type { MailOptions } from "@modules/integrations/mail"
|
|
2
3
|
import type { ObjectStorageOptions } from "@modules/integrations/object-storage"
|
|
3
4
|
import type { ServerOptions } from "@modules/platform/config"
|
|
4
5
|
import type { DatabaseConnectionOptions } from "@modules/platform/database"
|
|
@@ -19,4 +20,6 @@ export interface ApiOptions {
|
|
|
19
20
|
readonly passport: PassportOptions
|
|
20
21
|
/** S3-compatible object storage. */
|
|
21
22
|
readonly objectStorage: ObjectStorageOptions
|
|
23
|
+
/** Outgoing mail: the SMTP server and the app's own mail settings. */
|
|
24
|
+
readonly mail: MailOptions
|
|
22
25
|
}
|
|
@@ -2,7 +2,7 @@ import { Module } from "@nestjs/common"
|
|
|
2
2
|
import type { DynamicModule } from "@nestjs/common"
|
|
3
3
|
import { APP_FILTER, APP_GUARD } from "@nestjs/core"
|
|
4
4
|
import { AuthGuard, CredentialGuard } from "@modules/domain/identity"
|
|
5
|
-
import {
|
|
5
|
+
import { passportKit } from "@modules/integrations/passport"
|
|
6
6
|
import { ErrorsFilter } from "@modules/platform/errors"
|
|
7
7
|
import { OriginGuard, RateLimitGuard } from "@modules/platform/http-security"
|
|
8
8
|
import { platformModules } from "./compose/platform"
|
|
@@ -10,7 +10,7 @@ import { domainModules } from "./compose/domain"
|
|
|
10
10
|
import { systemModules } from "./compose/system"
|
|
11
11
|
import { apiModules } from "./compose/api"
|
|
12
12
|
import { webhooksModules } from "./compose/webhooks"
|
|
13
|
-
import { integrationProviders } from "./compose/integrations"
|
|
13
|
+
import { integrationModules, integrationProviders } from "./compose/integrations"
|
|
14
14
|
import type { ApiOptions } from "./api.options"
|
|
15
15
|
|
|
16
16
|
@Module({})
|
|
@@ -18,18 +18,19 @@ import type { ApiOptions } from "./api.options"
|
|
|
18
18
|
export class AppModule {
|
|
19
19
|
/** Builds the module from options parsed once at boot; nothing here reads the environment. */
|
|
20
20
|
static register(options: ApiOptions): DynamicModule {
|
|
21
|
-
const
|
|
21
|
+
const passport = passportKit(options.passport)
|
|
22
22
|
return {
|
|
23
23
|
module: AppModule,
|
|
24
24
|
imports: [
|
|
25
25
|
...platformModules(options),
|
|
26
|
-
...
|
|
26
|
+
...integrationModules(options),
|
|
27
|
+
...domainModules(passport.verify),
|
|
27
28
|
...systemModules(),
|
|
28
29
|
...apiModules(),
|
|
29
30
|
...webhooksModules(),
|
|
30
31
|
],
|
|
31
32
|
providers: [
|
|
32
|
-
...integrationProviders(options,
|
|
33
|
+
...integrationProviders(options, passport.verify),
|
|
33
34
|
{ provide: APP_FILTER, useClass: ErrorsFilter },
|
|
34
35
|
{ provide: APP_GUARD, useClass: RateLimitGuard },
|
|
35
36
|
{ provide: APP_GUARD, useClass: OriginGuard },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DynamicModule, Type } from "@nestjs/common"
|
|
2
|
+
import { AuthHttpModule } from "@features/api/auth"
|
|
2
3
|
|
|
3
|
-
/** The API transports of the app; `starci app add api` registers each generated door here. */
|
|
4
|
-
export const apiModules = (): Array<Type<unknown> | DynamicModule> => []
|
|
4
|
+
/** The API transports of the app: the sign-in doors; `starci app add api` registers each generated door here. */
|
|
5
|
+
export const apiModules = (): Array<Type<unknown> | DynamicModule> => [AuthHttpModule.register({})]
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
import type { Provider } from "@nestjs/common"
|
|
2
|
-
import { OWN_TOKEN_VERIFIER, PASSPORT_OPTIONS } from "@modules/integrations/passport"
|
|
1
|
+
import type { DynamicModule, Provider } from "@nestjs/common"
|
|
2
|
+
import { OWN_TOKEN_VERIFIER, PASSPORT_OPTIONS, passportModule } from "@modules/integrations/passport"
|
|
3
3
|
import type { VerifyAccessToken } from "@modules/integrations/passport"
|
|
4
|
-
import {
|
|
4
|
+
import { MailModule } from "@modules/integrations/mail"
|
|
5
|
+
import { ObjectStorageModule } from "@modules/integrations/object-storage"
|
|
5
6
|
import type { ApiOptions } from "../api.options"
|
|
6
7
|
|
|
7
|
-
/** The
|
|
8
|
+
/** The sign-in, object storage and mail packages of the app, each registered once with the app's typed settings. */
|
|
9
|
+
export const integrationModules = (options: ApiOptions): Array<DynamicModule> => [
|
|
10
|
+
passportModule(options.passport, { isGlobal: true }),
|
|
11
|
+
ObjectStorageModule.register({ ...options.objectStorage, isGlobal: true }),
|
|
12
|
+
MailModule.register({ ...options.mail, isGlobal: true }),
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
/** The passport integration ports bound once for this app. */
|
|
8
16
|
export const integrationProviders = (options: ApiOptions, verifyAccessToken: VerifyAccessToken): Array<Provider> => [
|
|
9
17
|
{ provide: PASSPORT_OPTIONS, useValue: options.passport },
|
|
10
18
|
{ provide: OWN_TOKEN_VERIFIER, useValue: verifyAccessToken },
|
|
11
|
-
{ provide: OBJECT_STORAGE, useValue: createObjectStorage(options.objectStorage) },
|
|
12
19
|
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { DomainError } from "@modules/platform/errors"
|
|
2
|
+
import type { ErrorKind } from "@modules/platform/errors"
|
|
3
|
+
|
|
4
|
+
/** Codes of the identity capability: the sign-in refusals travel under these wire codes, the same on both sides of the doors. */
|
|
5
|
+
export enum IdentityErrorCode {
|
|
6
|
+
/** The door needs a signed-in caller and the request establishes none. */
|
|
7
|
+
Unauthenticated = "IDENTITY_UNAUTHENTICATED",
|
|
8
|
+
/** The refresh token is unknown, expired or already spent. */
|
|
9
|
+
RefreshInvalid = "IDENTITY_REFRESH_INVALID",
|
|
10
|
+
/** A rotated refresh token was presented again: the session was ended. */
|
|
11
|
+
RefreshReused = "IDENTITY_REFRESH_REUSED",
|
|
12
|
+
/** The account is deleted or banned. */
|
|
13
|
+
AccountDisabled = "IDENTITY_ACCOUNT_DISABLED",
|
|
14
|
+
/** Too many failed attempts for this address; try again later. */
|
|
15
|
+
TooManyAttempts = "IDENTITY_TOO_MANY_ATTEMPTS",
|
|
16
|
+
/** The pair of address and password is wrong (the same answer for an unknown address). */
|
|
17
|
+
PairRefused = "IDENTITY_INVALID_CREDENTIALS",
|
|
18
|
+
/** The mailbox was never confirmed. */
|
|
19
|
+
EmailNotConfirmed = "IDENTITY_EMAIL_NOT_CONFIRMED",
|
|
20
|
+
/** The new password does not fit the policy (at least 8 characters, at most 72 bytes). */
|
|
21
|
+
PasswordInvalid = "IDENTITY_PASSWORD_INVALID",
|
|
22
|
+
/** The confirmation or reset link is unknown, expired or already spent. */
|
|
23
|
+
ResetTokenInvalid = "IDENTITY_RESET_TOKEN_INVALID",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** How each identity code travels. */
|
|
27
|
+
export const IDENTITY_ERROR_KINDS: Record<IdentityErrorCode, ErrorKind> = {
|
|
28
|
+
[IdentityErrorCode.Unauthenticated]: "unauthenticated",
|
|
29
|
+
[IdentityErrorCode.RefreshInvalid]: "unauthenticated",
|
|
30
|
+
[IdentityErrorCode.RefreshReused]: "unauthenticated",
|
|
31
|
+
[IdentityErrorCode.AccountDisabled]: "forbidden",
|
|
32
|
+
[IdentityErrorCode.TooManyAttempts]: "rate-limited",
|
|
33
|
+
[IdentityErrorCode.PairRefused]: "unauthenticated",
|
|
34
|
+
[IdentityErrorCode.EmailNotConfirmed]: "forbidden",
|
|
35
|
+
[IdentityErrorCode.PasswordInvalid]: "invalid",
|
|
36
|
+
[IdentityErrorCode.ResetTokenInvalid]: "invalid",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The one error class of the identity capability. */
|
|
40
|
+
export class IdentityError extends DomainError<IdentityErrorCode> {}
|
|
@@ -2,7 +2,7 @@ import { SetMetadata, applyDecorators, createParamDecorator } from "@nestjs/comm
|
|
|
2
2
|
import type { ExecutionContext } from "@nestjs/common"
|
|
3
3
|
import { injector } from "@modules/platform/composition"
|
|
4
4
|
import type { DoorDecorator, TypedParameterDecorator } from "@modules/platform/composition"
|
|
5
|
-
import type { VerifyAccessToken } from "@modules/integrations/passport"
|
|
5
|
+
import type { AuthDoors, AuthPasswords, AuthSessions, SignInBudget, VerifyAccessToken } from "@modules/integrations/passport"
|
|
6
6
|
import { ServerToServer, requestOf } from "@modules/platform/http-security"
|
|
7
7
|
import type { Principal } from "@modules/platform/cqrs"
|
|
8
8
|
import type { CredentialAuthenticator, CredentialMetadata } from "./credential.contracts"
|
|
@@ -10,6 +10,27 @@ import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
|
|
|
10
10
|
import { PublicReason } from "./identity.contracts"
|
|
11
11
|
import type { PublicMetadata } from "./identity.contracts"
|
|
12
12
|
|
|
13
|
+
/** Token of the sign-in doors of the package, provided by the identity module from the passport integration. */
|
|
14
|
+
export const IDENTITY_AUTH_DOORS: unique symbol = Symbol("domain.identity.auth-doors")
|
|
15
|
+
/** Token of the rotating sessions of the package. */
|
|
16
|
+
export const IDENTITY_AUTH_SESSIONS: unique symbol = Symbol("domain.identity.auth-sessions")
|
|
17
|
+
/** Token of the password hasher of the package. */
|
|
18
|
+
export const IDENTITY_AUTH_PASSWORDS: unique symbol = Symbol("domain.identity.auth-passwords")
|
|
19
|
+
/** Token of the failures-only sign-in budget of the package. */
|
|
20
|
+
export const IDENTITY_SIGN_IN_BUDGET: unique symbol = Symbol("domain.identity.sign-in-budget")
|
|
21
|
+
|
|
22
|
+
/** Injects the sign-in doors. Parameter type: AuthDoors. */
|
|
23
|
+
export const InjectAuthDoors = (): TypedParameterDecorator<AuthDoors> => injector<AuthDoors>(IDENTITY_AUTH_DOORS)
|
|
24
|
+
|
|
25
|
+
/** Injects the rotating sessions. Parameter type: AuthSessions. */
|
|
26
|
+
export const InjectAuthSessions = (): TypedParameterDecorator<AuthSessions> => injector<AuthSessions>(IDENTITY_AUTH_SESSIONS)
|
|
27
|
+
|
|
28
|
+
/** Injects the password hasher. Parameter type: AuthPasswords. */
|
|
29
|
+
export const InjectAuthPasswords = (): TypedParameterDecorator<AuthPasswords> => injector<AuthPasswords>(IDENTITY_AUTH_PASSWORDS)
|
|
30
|
+
|
|
31
|
+
/** Injects the sign-in budget. Parameter type: SignInBudget. */
|
|
32
|
+
export const InjectSignInBudget = (): TypedParameterDecorator<SignInBudget> => injector<SignInBudget>(IDENTITY_SIGN_IN_BUDGET)
|
|
33
|
+
|
|
13
34
|
/** Token of the access-token verifier the composition root hands to identity. */
|
|
14
35
|
export const ACCESS_TOKEN_VERIFIER: unique symbol = Symbol("domain.identity.access-token-verifier")
|
|
15
36
|
|
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common"
|
|
2
2
|
import type { DynamicModule, Provider } from "@nestjs/common"
|
|
3
|
+
import {
|
|
4
|
+
PASSPORT_ACCOUNTS,
|
|
5
|
+
PASSPORT_AUTHENTICATOR,
|
|
6
|
+
PASSPORT_DOORS,
|
|
7
|
+
PASSPORT_PASSWORDS,
|
|
8
|
+
PASSPORT_RATE_COUNTER,
|
|
9
|
+
PASSPORT_SESSIONS,
|
|
10
|
+
PASSPORT_SESSION_STORE,
|
|
11
|
+
PASSPORT_SIGN_IN_BUDGET,
|
|
12
|
+
} from "@modules/integrations/passport"
|
|
13
|
+
import { IdentityAccountDirectoryService } from "./account-directory.service"
|
|
3
14
|
import { AuthGuard } from "./auth.guard"
|
|
15
|
+
import { IdentityAuthenticatorService } from "./authenticator.service"
|
|
4
16
|
import { CredentialGuard } from "./credential.guard"
|
|
5
17
|
import { IdentityError, IdentityErrorCode } from "./errors/identity.error"
|
|
6
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
ACCESS_TOKEN_VERIFIER,
|
|
20
|
+
CREDENTIAL_AUTHENTICATOR,
|
|
21
|
+
IDENTITY_AUTH_DOORS,
|
|
22
|
+
IDENTITY_AUTH_PASSWORDS,
|
|
23
|
+
IDENTITY_AUTH_SESSIONS,
|
|
24
|
+
IDENTITY_SIGN_IN_BUDGET,
|
|
25
|
+
} from "./identity.decorators"
|
|
7
26
|
import { ConfigurableModuleClass, OPTIONS_TYPE } from "./identity.module-definition"
|
|
27
|
+
import { IdentityRateCounterService } from "./rate-counter.service"
|
|
28
|
+
import { IdentitySessionStoreService } from "./session-store.service"
|
|
29
|
+
import { IdentityEmailService } from "./identity-email.service"
|
|
30
|
+
import { IdentitySignInService } from "./sign-in.service"
|
|
8
31
|
|
|
9
32
|
const refusingAuthenticator: Provider = {
|
|
10
33
|
provide: CREDENTIAL_AUTHENTICATOR,
|
|
@@ -34,8 +57,33 @@ export class IdentityModule extends ConfigurableModuleClass {
|
|
|
34
57
|
authenticator,
|
|
35
58
|
CredentialGuard,
|
|
36
59
|
AuthGuard,
|
|
60
|
+
IdentitySessionStoreService,
|
|
61
|
+
IdentityAccountDirectoryService,
|
|
62
|
+
IdentityRateCounterService,
|
|
63
|
+
IdentityAuthenticatorService,
|
|
64
|
+
IdentitySignInService,
|
|
65
|
+
IdentityEmailService,
|
|
66
|
+
{ provide: IDENTITY_AUTH_DOORS, useExisting: PASSPORT_DOORS },
|
|
67
|
+
{ provide: IDENTITY_AUTH_SESSIONS, useExisting: PASSPORT_SESSIONS },
|
|
68
|
+
{ provide: IDENTITY_AUTH_PASSWORDS, useExisting: PASSPORT_PASSWORDS },
|
|
69
|
+
{ provide: IDENTITY_SIGN_IN_BUDGET, useExisting: PASSPORT_SIGN_IN_BUDGET },
|
|
70
|
+
{ provide: PASSPORT_SESSION_STORE, useExisting: IdentitySessionStoreService },
|
|
71
|
+
{ provide: PASSPORT_ACCOUNTS, useExisting: IdentityAccountDirectoryService },
|
|
72
|
+
{ provide: PASSPORT_RATE_COUNTER, useExisting: IdentityRateCounterService },
|
|
73
|
+
{ provide: PASSPORT_AUTHENTICATOR, useExisting: IdentityAuthenticatorService },
|
|
74
|
+
],
|
|
75
|
+
exports: [
|
|
76
|
+
CredentialGuard,
|
|
77
|
+
CREDENTIAL_AUTHENTICATOR,
|
|
78
|
+
AuthGuard,
|
|
79
|
+
ACCESS_TOKEN_VERIFIER,
|
|
80
|
+
IdentitySignInService,
|
|
81
|
+
IdentityEmailService,
|
|
82
|
+
PASSPORT_SESSION_STORE,
|
|
83
|
+
PASSPORT_ACCOUNTS,
|
|
84
|
+
PASSPORT_RATE_COUNTER,
|
|
85
|
+
PASSPORT_AUTHENTICATOR,
|
|
37
86
|
],
|
|
38
|
-
exports: [CredentialGuard, CREDENTIAL_AUTHENTICATOR, AuthGuard, ACCESS_TOKEN_VERIFIER],
|
|
39
87
|
}
|
|
40
88
|
}
|
|
41
89
|
}
|
|
@@ -5,3 +5,6 @@ export { IDENTITY_MESSAGES } from "./messages/identity.messages"
|
|
|
5
5
|
export { PublicReason } from "./identity.contracts"
|
|
6
6
|
export { ACCESS_TOKEN_VERIFIER, Public } from "./identity.decorators"
|
|
7
7
|
export { IdentityModule } from "./identity.module"
|
|
8
|
+
export { identityEntities } from "./persistence/entities"
|
|
9
|
+
export { IdentityEmailService } from "./identity-email.service"
|
|
10
|
+
export { IdentitySignInService } from "./sign-in.service"
|
package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { renderTransactional } from "@modules/integrations/mail"
|
|
2
|
+
import type { MailLanguage, RenderedMail } from "@modules/integrations/mail"
|
|
3
|
+
|
|
4
|
+
/** What a confirmation or reset mail is drawn from. */
|
|
5
|
+
export interface LinkMailInput {
|
|
6
|
+
/** The product name in the header. */
|
|
7
|
+
readonly brand: string
|
|
8
|
+
/** The absolute link the button opens. */
|
|
9
|
+
readonly url: string
|
|
10
|
+
/** How many hours the link lives. */
|
|
11
|
+
readonly hours: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface LinkCopy {
|
|
15
|
+
readonly subject: string
|
|
16
|
+
readonly paragraphs: (input: LinkMailInput) => readonly string[]
|
|
17
|
+
readonly button: string
|
|
18
|
+
readonly footer: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CONFIRM: Record<MailLanguage, LinkCopy> = {
|
|
22
|
+
vi: {
|
|
23
|
+
subject: "Xác nhận email của bạn",
|
|
24
|
+
paragraphs: ({ brand, hours }) => [`Cảm ơn bạn đã đăng ký ${brand}.`, `Nhấn nút bên dưới để xác nhận email. Liên kết có hiệu lực trong ${String(hours)} giờ và chỉ dùng được một lần.`],
|
|
25
|
+
button: "Xác nhận email",
|
|
26
|
+
footer: "Nếu bạn không đăng ký, hãy bỏ qua thư này.",
|
|
27
|
+
},
|
|
28
|
+
en: {
|
|
29
|
+
subject: "Confirm your email",
|
|
30
|
+
paragraphs: ({ brand, hours }) => [`Thanks for signing up to ${brand}.`, `Press the button below to confirm your email. The link works once and lasts ${String(hours)} hours.`],
|
|
31
|
+
button: "Confirm email",
|
|
32
|
+
footer: "If you did not sign up, ignore this message.",
|
|
33
|
+
},
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const RESET: Record<MailLanguage, LinkCopy> = {
|
|
37
|
+
vi: {
|
|
38
|
+
subject: "Đặt lại mật khẩu",
|
|
39
|
+
paragraphs: ({ hours }) => ["Chúng tôi nhận được yêu cầu đặt lại mật khẩu cho tài khoản của bạn.", `Nhấn nút bên dưới để chọn mật khẩu mới. Liên kết có hiệu lực trong ${String(hours)} giờ và chỉ dùng được một lần.`],
|
|
40
|
+
button: "Đặt lại mật khẩu",
|
|
41
|
+
footer: "Nếu bạn không yêu cầu, hãy bỏ qua thư này; mật khẩu của bạn không thay đổi.",
|
|
42
|
+
},
|
|
43
|
+
en: {
|
|
44
|
+
subject: "Reset your password",
|
|
45
|
+
paragraphs: ({ hours }) => ["We received a request to reset the password of your account.", `Press the button below to choose a new password. The link works once and lasts ${String(hours)} hours.`],
|
|
46
|
+
button: "Reset password",
|
|
47
|
+
footer: "If you did not ask for this, ignore this message; your password stays as it is.",
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const draw = (copy: LinkCopy, lang: MailLanguage, input: LinkMailInput): RenderedMail =>
|
|
52
|
+
renderTransactional({
|
|
53
|
+
lang,
|
|
54
|
+
subject: copy.subject,
|
|
55
|
+
brand: { name: input.brand },
|
|
56
|
+
paragraphs: copy.paragraphs(input),
|
|
57
|
+
action: { label: copy.button, url: input.url },
|
|
58
|
+
footer: copy.footer,
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
/** The mail that carries the link confirming a mailbox. */
|
|
62
|
+
export const confirmEmailMail = (lang: MailLanguage, input: LinkMailInput): RenderedMail => draw(CONFIRM[lang], lang, input)
|
|
63
|
+
|
|
64
|
+
/** The mail that carries the link choosing a new password. */
|
|
65
|
+
export const resetPasswordMail = (lang: MailLanguage, input: LinkMailInput): RenderedMail => draw(RESET[lang], lang, input)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MessageBundle } from "@modules/platform/i18n"
|
|
2
|
+
|
|
3
|
+
/** Display text of the identity codes, Vietnamese and English. */
|
|
4
|
+
export const IDENTITY_MESSAGES: MessageBundle = {
|
|
5
|
+
vi: {
|
|
6
|
+
"errors.IDENTITY_UNAUTHENTICATED":
|
|
7
|
+
"Bạn cần đăng nhập để thực hiện thao tác này.",
|
|
8
|
+
"errors.IDENTITY_REFRESH_INVALID": "Phiên đăng nhập không hợp lệ hoặc đã hết hạn.",
|
|
9
|
+
"errors.IDENTITY_REFRESH_REUSED": "Phiên đăng nhập đã kết thúc vì bị dùng lại. Hãy đăng nhập lại.",
|
|
10
|
+
"errors.IDENTITY_ACCOUNT_DISABLED": "Tài khoản này đã bị khóa.",
|
|
11
|
+
"errors.IDENTITY_TOO_MANY_ATTEMPTS": "Quá nhiều lần thử. Hãy thử lại sau.",
|
|
12
|
+
"errors.IDENTITY_INVALID_CREDENTIALS": "Email hoặc mật khẩu không đúng.",
|
|
13
|
+
"errors.IDENTITY_EMAIL_NOT_CONFIRMED": "Hãy xác nhận email trước khi đăng nhập.",
|
|
14
|
+
"errors.IDENTITY_PASSWORD_INVALID": "Mật khẩu cần ít nhất 8 ký tự và tối đa 72 byte.",
|
|
15
|
+
"errors.IDENTITY_RESET_TOKEN_INVALID": "Liên kết không hợp lệ hoặc đã hết hạn.",
|
|
16
|
+
},
|
|
17
|
+
en: {
|
|
18
|
+
"errors.IDENTITY_UNAUTHENTICATED": "You need to sign in to do this.",
|
|
19
|
+
"errors.IDENTITY_REFRESH_INVALID": "The session is invalid or has expired.",
|
|
20
|
+
"errors.IDENTITY_REFRESH_REUSED": "The session ended because it was used again. Please sign in again.",
|
|
21
|
+
"errors.IDENTITY_ACCOUNT_DISABLED": "This account is disabled.",
|
|
22
|
+
"errors.IDENTITY_TOO_MANY_ATTEMPTS": "Too many attempts. Try again later.",
|
|
23
|
+
"errors.IDENTITY_INVALID_CREDENTIALS": "The email or password is wrong.",
|
|
24
|
+
"errors.IDENTITY_EMAIL_NOT_CONFIRMED": "Confirm your email before signing in.",
|
|
25
|
+
"errors.IDENTITY_PASSWORD_INVALID": "The password needs at least 8 characters and at most 72 bytes.",
|
|
26
|
+
"errors.IDENTITY_RESET_TOKEN_INVALID": "The link is invalid or has expired.",
|
|
27
|
+
},
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { MAILER, MailError, escapeHtml, html, renderTransactional } from "@starci/mail"
|
|
2
|
+
export type { Mailer, MailMessage, RenderedMail, SentMail } from "@starci/mail"
|
|
3
|
+
export { parseMailConfig } from "./mail.config"
|
|
4
|
+
export { InjectMailSettings, InjectMailer, MAIL_SETTINGS } from "./mail.decorators"
|
|
5
|
+
export { MailModule } from "./mail.module"
|
|
6
|
+
export type { MailLanguage, MailOptions } from "./mail.options"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MailSecurity } from "@starci/mail"
|
|
2
|
+
import type { EnvSource } from "@modules/platform/config"
|
|
3
|
+
import type { MailLanguage, MailOptions } from "./mail.options"
|
|
4
|
+
|
|
5
|
+
const DEFAULT_PORT = 587
|
|
6
|
+
const CONFIRM_TTL_SECONDS = 86_400
|
|
7
|
+
const RESET_TTL_SECONDS = 3_600
|
|
8
|
+
|
|
9
|
+
const readSecurity = (env: EnvSource): MailSecurity => {
|
|
10
|
+
const value = env.optional("MAIL_SECURITY") ?? "starttls"
|
|
11
|
+
if (value !== "tls" && value !== "starttls" && value !== "none") throw new Error("MAIL_SECURITY must be tls, starttls or none")
|
|
12
|
+
return value
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const readLanguage = (env: EnvSource): MailLanguage => {
|
|
16
|
+
const value = env.optional("MAIL_DEFAULT_LANGUAGE") ?? "en"
|
|
17
|
+
if (value !== "vi" && value !== "en") throw new Error("MAIL_DEFAULT_LANGUAGE must be vi or en")
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Reads the mail configuration with no credential or server defaults; the login is optional and its password reads `<KEY>_FILE` first. */
|
|
22
|
+
export const parseMailConfig = (env: EnvSource): MailOptions => ({
|
|
23
|
+
host: env.string("MAIL_HOST"),
|
|
24
|
+
port: env.int("MAIL_PORT", DEFAULT_PORT),
|
|
25
|
+
security: readSecurity(env),
|
|
26
|
+
...(env.has("MAIL_USER") ? { auth: { user: env.string("MAIL_USER"), pass: env.secret("MAIL_PASSWORD") } } : {}),
|
|
27
|
+
from: { name: env.string("MAIL_FROM_NAME"), address: env.string("MAIL_FROM") },
|
|
28
|
+
webOrigin: new URL(env.url("AUTH_WEB_ORIGIN")).origin,
|
|
29
|
+
brandName: env.string("MAIL_FROM_NAME"),
|
|
30
|
+
defaultLanguage: readLanguage(env),
|
|
31
|
+
confirmTtlSeconds: env.int("MAIL_CONFIRM_TTL_SECONDS", CONFIRM_TTL_SECONDS),
|
|
32
|
+
resetTtlSeconds: env.int("MAIL_RESET_TTL_SECONDS", RESET_TTL_SECONDS),
|
|
33
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { MAILER } from "@starci/mail"
|
|
2
|
+
import type { Mailer } from "@starci/mail"
|
|
3
|
+
import { injector } from "@modules/platform/composition"
|
|
4
|
+
import type { TypedParameterDecorator } from "@modules/platform/composition"
|
|
5
|
+
import type { MailOptions } from "./mail.options"
|
|
6
|
+
|
|
7
|
+
/** Token of the app's mail settings (the brand, the default language, the link lifetimes). */
|
|
8
|
+
export const MAIL_SETTINGS: unique symbol = Symbol("integrations.mail.settings")
|
|
9
|
+
|
|
10
|
+
/** Injects the mail port of `@starci/mail`. Parameter type: Mailer. */
|
|
11
|
+
export const InjectMailer = (): TypedParameterDecorator<Mailer> => injector<Mailer>(MAILER)
|
|
12
|
+
|
|
13
|
+
/** Injects the app's mail settings. Parameter type: MailOptions. */
|
|
14
|
+
export const InjectMailSettings = (): TypedParameterDecorator<MailOptions> => injector<MailOptions>(MAIL_SETTINGS)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
|
|
2
|
+
import type { MailOptions } from "./mail.options"
|
|
3
|
+
|
|
4
|
+
const built = new ConfigurableModuleBuilder<MailOptions>()
|
|
5
|
+
.setExtras({ isGlobal: false }, (definition, extras) => ({ ...definition, global: extras.isGlobal }))
|
|
6
|
+
.build()
|
|
7
|
+
type Host = ConfigurableModuleHost<MailOptions, "register", "create", { isGlobal: boolean }>
|
|
8
|
+
|
|
9
|
+
/** The configurable-module base of the mail glue; `isGlobal` is decided by the app root. */
|
|
10
|
+
export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
|
|
11
|
+
/** The options of a synchronous registration, for the register signature. */
|
|
12
|
+
export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MailModule as PackageMailModule } from "@starci/mail"
|
|
2
|
+
import type { MailOptions as PackageMailOptions } from "@starci/mail"
|
|
3
|
+
import { Module } from "@nestjs/common"
|
|
4
|
+
import type { DynamicModule } from "@nestjs/common"
|
|
5
|
+
import { MAIL_SETTINGS } from "./mail.decorators"
|
|
6
|
+
import { ConfigurableModuleClass, OPTIONS_TYPE } from "./mail.module-definition"
|
|
7
|
+
|
|
8
|
+
@Module({})
|
|
9
|
+
/** Composes `@starci/mail` with the app's typed settings and provides the settings of the app's own mail. */
|
|
10
|
+
export class MailModule extends ConfigurableModuleClass {
|
|
11
|
+
/** Registers the capability once per app. */
|
|
12
|
+
static register(options: typeof OPTIONS_TYPE): DynamicModule {
|
|
13
|
+
const { webOrigin, brandName, defaultLanguage, confirmTtlSeconds, resetTtlSeconds, isGlobal, ...transport } = options
|
|
14
|
+
const base = super.register(options)
|
|
15
|
+
const mail = PackageMailModule.register({ ...(transport satisfies PackageMailOptions), isGlobal: false })
|
|
16
|
+
return {
|
|
17
|
+
...base,
|
|
18
|
+
imports: [...(base.imports ?? []), mail],
|
|
19
|
+
providers: [
|
|
20
|
+
...(base.providers ?? []),
|
|
21
|
+
{ provide: MAIL_SETTINGS, useValue: { ...transport, webOrigin, brandName, defaultLanguage, confirmTtlSeconds, resetTtlSeconds } },
|
|
22
|
+
],
|
|
23
|
+
exports: [...(base.exports ?? []), mail, MAIL_SETTINGS],
|
|
24
|
+
global: isGlobal === true,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { MailOptions as MailSettings } from "@starci/mail"
|
|
2
|
+
|
|
3
|
+
/** The languages the app writes its mail in. */
|
|
4
|
+
export type MailLanguage = "vi" | "en"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Outgoing mail parsed once at API boot: the settings of `@starci/mail` and what the app's own mail needs (the product name in
|
|
8
|
+
* the header, the language of a mail whose request names none, how long a confirmation or reset link lives). The words and
|
|
9
|
+
* layout of each mail belong to the domain that sends it.
|
|
10
|
+
*/
|
|
11
|
+
export interface MailOptions extends MailSettings {
|
|
12
|
+
/** The origin of the web app: the links of a confirmation or reset mail open its pages. */
|
|
13
|
+
readonly webOrigin: string
|
|
14
|
+
/** The product name in the header of every mail. */
|
|
15
|
+
readonly brandName: string
|
|
16
|
+
/** The language of a mail whose request names none. */
|
|
17
|
+
readonly defaultLanguage: MailLanguage
|
|
18
|
+
/** How long a confirmation link lives, in seconds. */
|
|
19
|
+
readonly confirmTtlSeconds: number
|
|
20
|
+
/** How long a password reset link lives, in seconds. */
|
|
21
|
+
readonly resetTtlSeconds: number
|
|
22
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { OBJECT_STORAGE } from "@starci/object-storage"
|
|
2
2
|
export { parseObjectStorageConfig } from "./object-storage.config"
|
|
3
|
-
export {
|
|
4
|
-
export type {
|
|
3
|
+
export { ObjectStorageModule } from "./object-storage.module"
|
|
4
|
+
export type { ObjectStorageOptions } from "./object-storage.options"
|
package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.config.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { EnvSource } from "@modules/platform/config"
|
|
2
2
|
import type { ObjectStorageOptions } from "./object-storage.options"
|
|
3
3
|
|
|
4
|
+
const PRESIGN_TTL_SECONDS = 300
|
|
5
|
+
|
|
4
6
|
/** Reads the object storage configuration with no credential or endpoint defaults; secrets read `<KEY>_FILE` first. */
|
|
5
7
|
export const parseObjectStorageConfig = (env: EnvSource): ObjectStorageOptions => ({
|
|
6
8
|
endpoint: env.url("STORAGE_ENDPOINT"),
|
|
@@ -8,5 +10,5 @@ export const parseObjectStorageConfig = (env: EnvSource): ObjectStorageOptions =
|
|
|
8
10
|
bucket: env.string("STORAGE_BUCKET"),
|
|
9
11
|
accessKeyId: env.secret("STORAGE_ACCESS_KEY_ID"),
|
|
10
12
|
secretAccessKey: env.secret("STORAGE_SECRET_ACCESS_KEY"),
|
|
11
|
-
|
|
13
|
+
defaultExpiresIn: env.int("STORAGE_PRESIGN_TTL_SECONDS", PRESIGN_TTL_SECONDS),
|
|
12
14
|
})
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { OBJECT_STORAGE } from "@starci/object-storage"
|
|
2
|
+
import type { ObjectStorage } from "@starci/object-storage"
|
|
1
3
|
import { injector } from "@modules/platform/composition"
|
|
2
4
|
import type { TypedParameterDecorator } from "@modules/platform/composition"
|
|
3
|
-
import type { ObjectStorage } from "./object-storage.options"
|
|
4
5
|
|
|
5
|
-
/** Token of the
|
|
6
|
-
export const
|
|
6
|
+
/** Token of the bucket of the app's objects. */
|
|
7
|
+
export const OBJECT_STORAGE_BUCKET: unique symbol = Symbol("integrations.object-storage.bucket")
|
|
7
8
|
|
|
8
|
-
/** Injects the object storage port
|
|
9
|
+
/** Injects the object storage port of `@starci/object-storage`. Parameter type: ObjectStorage. */
|
|
9
10
|
export const InjectObjectStorage = (): TypedParameterDecorator<ObjectStorage> => injector<ObjectStorage>(OBJECT_STORAGE)
|
|
11
|
+
|
|
12
|
+
/** Injects the bucket of the app's objects. Parameter type: string. */
|
|
13
|
+
export const InjectObjectStorageBucket = (): TypedParameterDecorator<string> => injector<string>(OBJECT_STORAGE_BUCKET)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { defineCapabilityModule } from "@starci/nest-kit"
|
|
2
|
+
import type { ObjectStorageOptions } from "./object-storage.options"
|
|
3
|
+
|
|
4
|
+
/** The configurable-module base of the object storage glue; `isGlobal` is decided by the app root. */
|
|
5
|
+
export const { ConfigurableModuleClass, OPTIONS_TYPE } = defineCapabilityModule<ObjectStorageOptions>()
|
package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ObjectStorageModule as StorageModule } from "@starci/object-storage"
|
|
2
|
+
import type { ObjectStorageOptions as StorageSettings } from "@starci/object-storage"
|
|
3
|
+
import { Module } from "@nestjs/common"
|
|
4
|
+
import type { DynamicModule } from "@nestjs/common"
|
|
5
|
+
import { CLOCK } from "@modules/platform/clock"
|
|
6
|
+
import type { Clock } from "@modules/platform/clock"
|
|
7
|
+
import { OBJECT_STORAGE_BUCKET } from "./object-storage.decorators"
|
|
8
|
+
import { ConfigurableModuleClass, OPTIONS_TYPE } from "./object-storage.module-definition"
|
|
9
|
+
|
|
10
|
+
@Module({})
|
|
11
|
+
/** Composes `@starci/object-storage` with the app's typed settings and clock, and provides the bucket of the app's objects. */
|
|
12
|
+
export class ObjectStorageModule extends ConfigurableModuleClass {
|
|
13
|
+
/** Registers the capability once per app. */
|
|
14
|
+
static register(options: typeof OPTIONS_TYPE): DynamicModule {
|
|
15
|
+
const { bucket, isGlobal, ...settings } = options
|
|
16
|
+
const base = super.register(options)
|
|
17
|
+
const storage = StorageModule.registerAsync({
|
|
18
|
+
inject: [CLOCK],
|
|
19
|
+
useFactory: (clock: Clock): StorageSettings => ({ ...settings, clock }),
|
|
20
|
+
})
|
|
21
|
+
return {
|
|
22
|
+
...base,
|
|
23
|
+
imports: [...(base.imports ?? []), storage],
|
|
24
|
+
providers: [...(base.providers ?? []), { provide: OBJECT_STORAGE_BUCKET, useValue: bucket }],
|
|
25
|
+
exports: [...(base.exports ?? []), storage, OBJECT_STORAGE_BUCKET],
|
|
26
|
+
global: isGlobal === true,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|