@starci/hfs 5.2.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.
Files changed (215) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +1 -1
  3. package/cache/hfs-family.d.mts +2 -3
  4. package/cache/hfs-family.mjs +4 -9
  5. package/cache/secret-records.d.mts +7 -0
  6. package/cache/secret-records.mjs +12 -0
  7. package/cache/session.mjs +4 -2
  8. package/codemod/account-doors.d.mts +36 -0
  9. package/codemod/account-doors.mjs +128 -0
  10. package/codemod/main.mjs +31 -1
  11. package/codemod/module-definition.d.mts +52 -0
  12. package/codemod/module-definition.mjs +242 -0
  13. package/codemod/quality-binding.d.mts +33 -0
  14. package/codemod/quality-binding.mjs +38 -0
  15. package/codemod/secrets-cache.d.mts +35 -0
  16. package/codemod/secrets-cache.mjs +70 -0
  17. package/codemod/sops-rules.d.mts +34 -0
  18. package/codemod/sops-rules.mjs +69 -0
  19. package/codemod/theme-tokens.mjs +48 -2
  20. package/lint/run.d.mts +6 -0
  21. package/lint/run.mjs +63 -10
  22. package/package.json +1 -1
  23. package/runtime/knowledge/hfs/canon-pins.yaml +43 -10
  24. package/runtime/knowledge/hfs/fe-stack.yaml +3 -3
  25. package/runtime/knowledge/hfs/rules.yaml +13 -9
  26. package/runtime/knowledge/hfs/slots.yaml +105 -12
  27. package/runtime/modules/kernel/failure-codes.yaml +5 -4
  28. package/runtime/scripts/hfs/architecture/config.d.mts +4 -0
  29. package/runtime/scripts/hfs/architecture/config.mjs +25 -1
  30. package/runtime/scripts/hfs/architecture/module-per-transport.d.mts +1 -1
  31. package/runtime/scripts/hfs/architecture/module-per-transport.mjs +6 -1
  32. package/runtime/scripts/hfs/architecture/root-packages.d.mts +26 -0
  33. package/runtime/scripts/hfs/architecture/root-packages.mjs +78 -0
  34. package/runtime/scripts/hfs/architecture/type-context.d.mts +4 -1
  35. package/runtime/scripts/hfs/architecture/type-context.mjs +30 -0
  36. package/runtime/scripts/hfs/architecture/typescript.d.mts +7 -1
  37. package/runtime/scripts/hfs/architecture/typescript.mjs +21 -1
  38. package/runtime/scripts/hfs/explicit-surface-rule.mjs +1 -1
  39. package/runtime/scripts/hfs/rules/database-access.mjs +39 -1
  40. package/runtime/scripts/hfs/rules/monorepo.d.mts +4 -0
  41. package/runtime/scripts/hfs/rules/monorepo.mjs +10 -5
  42. package/runtime/scripts/hfs/rules/secrets.mjs +11 -0
  43. package/runtime/scripts/hfs/secret-errors.d.mts +3 -0
  44. package/runtime/scripts/hfs/secret-errors.mjs +8 -0
  45. package/runtime/scripts/hfs/secret-init.d.mts +6 -0
  46. package/runtime/scripts/hfs/secret-init.mjs +23 -0
  47. package/runtime/scripts/hfs/secret-sops.mjs +9 -1
  48. package/runtime/scripts/hfs/secret-standard.d.mts +20 -0
  49. package/runtime/scripts/hfs/secret-standard.mjs +106 -0
  50. package/runtime/scripts/hfs/secret.mjs +5 -1
  51. package/runtime/scripts/hfs/view.d.mts +23 -0
  52. package/runtime/scripts/hfs/view.mjs +68 -0
  53. package/runtime/scripts/lib/declaration-surface.d.mts +4 -0
  54. package/runtime/scripts/lib/declaration-surface.mjs +55 -1
  55. package/runtime/scripts/lib/secret-records.d.mts +13 -0
  56. package/runtime/scripts/lib/secret-records.mjs +83 -0
  57. package/scaffold/add-inbox-lite.mjs +2 -2
  58. package/scaffold/add-queue-lite.mjs +1 -3
  59. package/scaffold/app.d.mts +2 -2
  60. package/scaffold/app.mjs +10 -12
  61. package/scaffold/lite-exports.mjs +1 -15
  62. package/scripts/sync-bundles.d.mts +8 -0
  63. package/stack/images/openclaw-runtime/Dockerfile +18 -0
  64. package/stack/images/openclaw-runtime/entrypoint.sh +23 -0
  65. package/stack/images/openclaw-runtime/openclaw-init.mjs +101 -0
  66. package/stack/images/openclaw-runtime/openclaw-relay.mjs +25 -0
  67. package/stack/local-up.mjs +3 -0
  68. package/stack/runtime-layers.d.mts +21 -0
  69. package/stack/runtime-layers.mjs +67 -0
  70. package/sync/hygiene.mjs +3 -1
  71. package/sync/index.d.mts +2 -0
  72. package/sync/index.mjs +33 -2
  73. package/templates/app/auth-doors.sql.partial +115 -0
  74. package/templates/app/ci-workflows/github/workflows/ci.yml +1 -1
  75. package/templates/app/ci-workflows-lite/github/workflows/ci.yml +1 -1
  76. package/templates/app/package-scripts/core.json +1 -1
  77. package/templates/app/package-scripts/package.json +1 -1
  78. package/templates/app/package-scripts-lite/package.json +1 -1
  79. package/templates/app/skeleton-lite/.starcistacks/application-stacks.yaml +9 -0
  80. package/templates/app/skeleton-lite/.starcistacks/dev/README.md +3 -1
  81. package/templates/app/skeleton-lite/.starcistacks/dev/infra/compose/compose.yaml +19 -1
  82. package/templates/app/skeleton-lite/.starcistacks/dev/runtime/env/KEYS.md +3 -0
  83. package/templates/app/skeleton-lite/.starcistacks/production/infra/swarm/stack.yaml +5 -0
  84. package/templates/app/skeleton-lite/.starcistacks/production/runtime/env/KEYS.md +4 -0
  85. package/templates/app/skeleton-lite/db/migrations/__timestamp___baseline.sql +24 -4
  86. package/templates/be/auth-lite/src/features/api/auth/application/auth.contracts.ts +33 -0
  87. package/templates/be/auth-lite/src/features/api/auth/application/confirm.command.ts +11 -0
  88. package/templates/be/auth-lite/src/features/api/auth/application/confirm.handler.ts +22 -0
  89. package/templates/be/auth-lite/src/features/api/auth/application/forgot.command.ts +11 -0
  90. package/templates/be/auth-lite/src/features/api/auth/application/forgot.handler.ts +22 -0
  91. package/templates/be/auth-lite/src/features/api/auth/application/resend.command.ts +11 -0
  92. package/templates/be/auth-lite/src/features/api/auth/application/resend.handler.ts +22 -0
  93. package/templates/be/auth-lite/src/features/api/auth/application/reset.command.ts +11 -0
  94. package/templates/be/auth-lite/src/features/api/auth/application/reset.handler.ts +22 -0
  95. package/templates/be/auth-lite/src/features/api/auth/application/signup.command.ts +11 -0
  96. package/templates/be/auth-lite/src/features/api/auth/application/signup.handler.ts +22 -0
  97. package/templates/be/auth-lite/src/features/api/auth/auth.module-definition.ts +3 -10
  98. package/templates/be/auth-lite/src/features/api/auth/auth.module.ts +6 -1
  99. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth-http.module-definition.ts +3 -10
  100. package/templates/be/auth-lite/src/features/api/auth/transport/http/auth.controller.ts +56 -1
  101. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/forgot.request.ts +16 -0
  102. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/resend.request.ts +16 -0
  103. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/reset.request.ts +18 -0
  104. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/signup.request.ts +22 -0
  105. package/templates/be/auth-lite/src/features/api/auth/transport/http/dto/token.request.ts +11 -0
  106. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.spec.ts +30 -30
  107. package/templates/be/auth-lite/src/modules/domain/identity/account-directory.service.ts +7 -16
  108. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.spec.ts +113 -49
  109. package/templates/be/auth-lite/src/modules/domain/identity/authenticator.service.ts +42 -53
  110. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.log-events.ts +6 -0
  111. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.spec.ts +233 -0
  112. package/templates/be/auth-lite/src/modules/domain/identity/identity-email.service.ts +176 -0
  113. package/templates/be/auth-lite/src/modules/domain/identity/identity-sign-in.log-events.ts +6 -0
  114. package/templates/be/auth-lite/src/modules/domain/identity/persistence/account-doors.port.ts +98 -0
  115. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/identity-email-token.entity.ts +34 -0
  116. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/index.ts +3 -10
  117. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.spec.ts +17 -0
  118. package/templates/be/auth-lite/src/modules/domain/identity/sign-in.service.ts +6 -0
  119. package/templates/be/auth-lite/src/tests/fixtures/builders/identity.builder.ts +29 -33
  120. package/templates/be/inbox/module-definition.ts.tpl +2 -9
  121. package/templates/be/inbox/platform/event-bus/event-bus.module-definition.ts.tpl +2 -11
  122. package/templates/be/inbox/platform/inbox/inbox.module-definition.ts.tpl +2 -9
  123. package/templates/be/patterns/api/feature.module-definition.ts.tpl +3 -9
  124. package/templates/be/patterns/api/graphql.module-definition.ts.tpl +3 -9
  125. package/templates/be/patterns/cli/group.module-definition.ts.tpl +2 -9
  126. package/templates/be/patterns/event-bus/platform/event-bus.module-definition.ts.tpl +3 -10
  127. package/templates/be/patterns/idempotency/platform/inbox/inbox.module-definition.ts.tpl +2 -9
  128. package/templates/be/patterns/jobs/job.module-definition.ts.tpl +2 -9
  129. package/templates/be/patterns/jobs/job.queue.module-definition.ts.tpl +3 -9
  130. package/templates/be/patterns/jobs/platform/jobs.module-definition.ts.tpl +3 -10
  131. package/templates/be/patterns/projections/projection.module-definition.ts.tpl +2 -9
  132. package/templates/be/patterns/queues/platform/queue.module-definition.ts.tpl +3 -10
  133. package/templates/be/patterns/queues/queue.module-definition.ts.tpl +2 -9
  134. package/templates/be/patterns/reactors/message.module-definition.ts.tpl +3 -9
  135. package/templates/be/patterns/reactors/reactor.module-definition.ts.tpl +2 -9
  136. package/templates/be/patterns/realtime/channel.graphql.module-definition.ts.tpl +3 -9
  137. package/templates/be/patterns/realtime/channel.sse.module-definition.ts.tpl +3 -9
  138. package/templates/be/patterns/realtime/channel.websocket.module-definition.ts.tpl +3 -9
  139. package/templates/be/patterns/saga/saga.message.module-definition.ts.tpl +3 -9
  140. package/templates/be/patterns/saga/saga.module-definition.ts.tpl +2 -9
  141. package/templates/be/patterns/webhooks/webhook.http.module-definition.ts.tpl +3 -9
  142. package/templates/be/platform/graphql/graphql.module-definition.ts +2 -9
  143. package/templates/be/skeleton/src/features/api/system-health/system-health.module-definition.ts +3 -9
  144. package/templates/be/skeleton/src/features/api/system-health/transport/http/system-health-http.module-definition.ts +3 -9
  145. package/templates/be/skeleton/src/features/cli/cli.module-definition.ts +2 -9
  146. package/templates/be/skeleton/src/features/cli/migrate/migrate.module-definition.ts +3 -9
  147. package/templates/be/skeleton/src/features/cli/seed/seed.module-definition.ts +2 -9
  148. package/templates/be/skeleton/src/modules/domain/identity/identity.module-definition.ts +2 -9
  149. package/templates/be/skeleton/src/modules/domain/liveness/liveness.module-definition.ts +2 -9
  150. package/templates/be/skeleton/src/modules/domain/note/note.module-definition.ts +2 -9
  151. package/templates/be/skeleton/src/modules/platform/clock/clock.module-definition.ts +2 -9
  152. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.contracts.ts +2 -0
  153. package/templates/be/skeleton/src/modules/platform/cqrs/cqrs.module-definition.ts +2 -9
  154. package/templates/be/skeleton/src/modules/platform/database/database.module-definition.ts +3 -13
  155. package/templates/be/skeleton/src/modules/platform/errors/errors.module-definition.ts +4 -11
  156. package/templates/be/skeleton/src/modules/platform/http/http.module-definition.ts +4 -9
  157. package/templates/be/skeleton/src/modules/platform/http-security/http-security.module-definition.ts +5 -11
  158. package/templates/be/skeleton/src/modules/platform/i18n/i18n.module-definition.ts +4 -11
  159. package/templates/be/skeleton/src/modules/platform/ids/ids.module-definition.ts +2 -9
  160. package/templates/be/skeleton/src/modules/platform/logging/logging.module-definition.ts +2 -9
  161. package/templates/be/skeleton/src/modules/platform/retry/retry.module-definition.ts +2 -9
  162. package/templates/be/skeleton-lite/apps/api/src/api.env.ts +4 -0
  163. package/templates/be/skeleton-lite/apps/api/src/api.options.ts +3 -0
  164. package/templates/be/skeleton-lite/apps/api/src/compose/integrations.ts +3 -1
  165. package/templates/be/skeleton-lite/src/modules/domain/identity/errors/identity.error.ts +6 -0
  166. package/templates/be/skeleton-lite/src/modules/domain/identity/identity.module.ts +3 -0
  167. package/templates/be/skeleton-lite/src/modules/domain/identity/index.ts +1 -0
  168. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity-mail.messages.ts +65 -0
  169. package/templates/be/skeleton-lite/src/modules/domain/identity/messages/identity.messages.ts +4 -0
  170. package/templates/be/skeleton-lite/src/modules/integrations/mail/index.ts +6 -0
  171. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.config.ts +33 -0
  172. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.decorators.ts +14 -0
  173. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module-definition.ts +12 -0
  174. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.module.ts +27 -0
  175. package/templates/be/skeleton-lite/src/modules/integrations/mail/mail.options.ts +22 -0
  176. package/templates/be/skeleton-lite/src/modules/integrations/object-storage/object-storage.module-definition.ts +2 -9
  177. package/templates/be/skeleton-lite/src/modules/integrations/passport/index.ts +1 -1
  178. package/templates/be/skeleton-lite/src/modules/platform/database/index.ts +1 -0
  179. package/templates/be/skeleton-lite/src/modules/platform/http-security/rate-limit.guard.spec.ts +11 -0
  180. package/templates/be/skeleton-lite/src/tests/fixtures/builders/api.builder.ts +11 -0
  181. package/templates/be/table/api/feature.module-definition.ts.tpl +3 -9
  182. package/templates/be/table/api/graphql.module-definition.ts.tpl +3 -9
  183. package/templates/be/table/module-definition.ts.tpl +2 -9
  184. package/templates/fe/auth-lite/apps/web/src/modules/auth/email-doors.ts +16 -0
  185. package/templates/fe/auth-lite/apps/web/src/modules/auth/index.ts +5 -0
  186. package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +30 -67
  187. package/templates/fe/skeleton/packages/__project__-i18n/src/proxy.ts +6 -21
  188. package/templates/fe/skeleton/packages/__project__-i18n/src/routing.ts +10 -16
  189. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/confirm/page.tsx +9 -0
  190. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/forgot/page.tsx +9 -0
  191. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/resend/page.tsx +9 -0
  192. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/reset/page.tsx +9 -0
  193. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/auth/signup/page.tsx +9 -0
  194. package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +5 -9
  195. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/component.tsx +51 -0
  196. package/templates/fe/skeleton-lite/apps/web/src/components/blocks/EmailDoorForm/index.tsx +84 -0
  197. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppConfirmPage/index.tsx +25 -0
  198. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppForgotPage/index.tsx +21 -0
  199. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/component.tsx +11 -3
  200. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppHomePage/index.tsx +4 -1
  201. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResendPage/index.tsx +21 -0
  202. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppResetPage/index.tsx +25 -0
  203. package/templates/fe/skeleton-lite/apps/web/src/features/pages/AppSignupPage/index.tsx +21 -0
  204. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/emailAuth.shared.ts +12 -0
  205. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/index.ts +2 -0
  206. package/templates/fe/skeleton-lite/apps/web/src/hooks/emailAuth/useEmailDoor.ts +32 -0
  207. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/index.ts +2 -2
  208. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/en.json +50 -1
  209. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/messages/vi.json +50 -1
  210. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +7 -30
  211. package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/routing.ts +13 -14
  212. package/templates/fe/skeleton-lite/apps/web/src/modules/routes/index.ts +5 -0
  213. package/templates/fe/skeleton-lite/apps/web/src/proxy.ts +4 -17
  214. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts +0 -38
  215. package/templates/be/auth-lite/src/modules/domain/identity/persistence/entities/auth-user.entity.ts +0 -54
package/sync/index.mjs CHANGED
@@ -237,6 +237,9 @@ export function sonarScope(presets) {
237
237
  const tests = preset.filter(glob => !glob.endsWith('/**'));
238
238
  return { tests, exclusions: [...new Set([...preset, '**/.next/**', '**/node_modules/**'])] };
239
239
  }
240
+ /** The managed ESLint config at the app root that lints the root workspace packages. */
241
+ export const PACKAGES_LINT_FILE = 'eslint.config.mjs';
242
+ const LINE = '\n';
240
243
  const SOURCE_FILE = /\.[cm]?ts$/;
241
244
  const SOURCE_SKIPPED = new Set(['node_modules', 'dist', 'coverage']);
242
245
  /**
@@ -396,6 +399,30 @@ const moduleExclusions = (dirs) => dirs.flatMap(dir => [`${dir}/src/workbench/**
396
399
  function registryTargets(app, source) {
397
400
  return registryFiles(app.project, source?.modules ?? []).map((file) => ({ path: file.path, scope: APP_SCOPE, mode: 'file', content: file.content, hash: hashOf(file.content) }));
398
401
  }
402
+ /**
403
+ * The root `eslint.config.mjs` of an app that has root workspace packages (packages/agent-sdk, packages/module-<name>, the registry): no side config
404
+ * reaches `packages/`, so this managed file lints their source by subpath. The back-end canon judges everything but the front-end subpaths
405
+ * (`src/workbench/` outside its contract, `src/public/`), the front-end canon and the selected Grammar family those, each over the package view of
406
+ * the app root. An app without a root package has no such file; the family factory for packages is the family's `<name>GrammarPackagesConfig`.
407
+ */
408
+ function packagesLintTargets(app, source) {
409
+ if (!(source?.modules?.length || source?.otherPackages?.length))
410
+ return [];
411
+ const grammar = grammarLintFor(app.sides.fe.grammar);
412
+ const factory = grammar.package ? grammar.factory.replace(/Config$/, 'PackagesConfig') : null;
413
+ const content = [
414
+ 'import { loadPackagesHfs as loadBePackages, starciBePackagesConfig } from "@starci/eslint-canon-be"',
415
+ 'import { loadPackagesHfs as loadFePackages, starciFePackagesConfig } from "@starci/eslint-canon-fe"',
416
+ ...(grammar.package ? [`import { ${factory} } from "${grammar.package}"`] : []),
417
+ '',
418
+ 'const be = loadBePackages(import.meta.url)',
419
+ 'const fe = loadFePackages(import.meta.url)',
420
+ '',
421
+ `export default [...(await starciBePackagesConfig({ hfs: be })), ...starciFePackagesConfig({ hfs: fe })${grammar.package ? `, ...${factory}({ hfs: fe })` : ''}]`,
422
+ '',
423
+ ].join(LINE);
424
+ return [{ path: PACKAGES_LINT_FILE, scope: APP_SCOPE, mode: 'file', content, hash: hashOf(content) }];
425
+ }
399
426
  export function variables(app, scope, presets, sonarKey, manifest = loadSlotManifest(), source = { files: [], read: () => '' }) {
400
427
  const stack = source.stack ?? { images: [], passEnv: [] };
401
428
  const fe = app.sides.fe;
@@ -429,7 +456,9 @@ export function variables(app, scope, presets, sonarKey, manifest = loadSlotMani
429
456
  typecheck: `${modulesFirst}npm run codegen --silent && tsc -p be/tsconfig.json && turbo run typecheck`,
430
457
  // The typed lint reads the types the fe packages declare (their dist), so the packages are built first.
431
458
  lintPrefix: `${modulesFirst}${packages ? 'npm run codegen --silent && turbo run build --filter=./fe/packages/* && ' : 'npm run codegen --silent && '}`,
432
- moduleTests: modules.length ? ' && npm run test:modules' : '',
459
+ // The unit run, the back-end build and every CI job that imports a module package find it built (build:modules precedes them); the logic of a package is held at 100 per file by its own jest run, a step of the fast job.
460
+ modulesFirst,
461
+ moduleTestStep: modules.length ? ' - name: module packages\n run: npm run test:modules\n' : '',
433
462
  nodeMajor: String(NODE_MAJOR),
434
463
  // The deploy server's age identity the restore workflow writes (stack/server-key.mjs owns the path).
435
464
  serverAgeIdentity: SERVER_AGE_IDENTITY,
@@ -503,7 +532,7 @@ function renderOnce(hfs, presets, { sonarKey, readTemplate = readBundled, manife
503
532
  declaredTargets.push({ path: 'README.md', mode: 'block', markdown: true, template: 'app/readme-badges/README.md' });
504
533
  const targets = declaredTargets
505
534
  .filter(target => !(app.edition === 'lite' && source?.workPresent === false && target.path === '.starciwork/.gitignore'));
506
- return [...(scope === APP_SCOPE ? registryTargets(app, source) : []), ...targets.map((target) => {
535
+ return [...(scope === APP_SCOPE ? [...registryTargets(app, source), ...packagesLintTargets(app, source)] : []), ...targets.map((target) => {
507
536
  const template = target.path === 'codecov.yml' && source?.quality?.codecov.enabled === false
508
537
  ? '{{header}}\n# Codecov is disabled in the manifest.\ncodecov:\n require_ci_to_pass: false\n'
509
538
  : readTemplate(target.template);
@@ -657,6 +686,8 @@ const LITE_RETIRED_DIRS = Object.freeze(['supabase/types']);
657
686
  /** The files a lite app's sync retires, each with the reason the dry run prints. */
658
687
  const LITE_RETIRED_FILES = Object.freeze({
659
688
  [RETIRED_DB_DOCKERFILE]: `the db image moved to ${DB_DOCKERFILE} (ruling lite-db-image-in-stacks)`,
689
+ 'be/src/modules/domain/identity/persistence/entities/auth-user.entity.ts': 'the sign-in reaches accounts through the account doors (be/src/modules/domain/identity/persistence/account-doors.port.ts); the application role holds no grant on schema auth',
690
+ 'be/src/modules/domain/identity/persistence/entities/auth-identity.entity.ts': 'the sign-in reaches accounts through the account doors (be/src/modules/domain/identity/persistence/account-doors.port.ts); the application role holds no grant on schema auth',
660
691
  '.github/workflows/db-deploy.yml': 'the stack deploy migrates the database (a verified backup, the expand migrations before the services, the contract migrations after the accept; ruling app-cicd-auto-deploy)',
661
692
  });
662
693
  /** Why a retired file goes: a retired file's own reason, else the folder's. */
@@ -0,0 +1,115 @@
1
+ -- The account doors of a lite app: the only way app_be reaches auth.users and auth.identities (it holds no grant on schema auth). Eight
2
+ -- SECURITY DEFINER functions with a pinned search_path, EXECUTE revoked from public and the browser roles and granted to app_be alone: an
3
+ -- account comes back only for an exact address or id, a hash only for an exact address, and no door writes role or app metadata; the hash
4
+ -- is written only by a new account and by be_auth_set_password.
5
+ -- The text is the canon owner of the doors (templates/app/auth-doors.sql.partial); the back end calls them through persistence/account-doors.port.ts.
6
+
7
+ -- Looks up an address in lower case; the bootstrap index serves email as written, not lower(email).
8
+ create index if not exists users_email_lower_idx on auth.users (lower(email)) where is_anonymous = false;
9
+
10
+ -- A person by sign-in address (lower-cased), with the stored bcrypt hash so the back end can verify it.
11
+ CREATE FUNCTION public.be_auth_user_by_email(p_email text)
12
+ RETURNS TABLE(id uuid, email text, encrypted_password text, email_confirmed_at timestamp with time zone, disabled boolean)
13
+ LANGUAGE sql STABLE SECURITY DEFINER
14
+ SET search_path TO ''
15
+ AS $$
16
+ select u.id, u.email::text, u.encrypted_password::text, u.email_confirmed_at,
17
+ (u.deleted_at is not null or (u.banned_until is not null and u.banned_until > now()))
18
+ from auth.users u
19
+ where lower(u.email) = p_email and u.is_anonymous = false
20
+ order by u.created_at asc nulls last
21
+ limit 1;
22
+ $$;
23
+
24
+ -- One person by id.
25
+ CREATE FUNCTION public.be_auth_user_by_id(p_user uuid)
26
+ RETURNS TABLE(id uuid, email text, last_sign_in_at timestamp with time zone, meta jsonb, disabled boolean)
27
+ LANGUAGE sql STABLE SECURITY DEFINER
28
+ SET search_path TO ''
29
+ AS $$
30
+ select u.id, u.email::text, u.last_sign_in_at, coalesce(u.raw_user_meta_data, '{}'::jsonb),
31
+ (u.deleted_at is not null or (u.banned_until is not null and u.banned_until > now()))
32
+ from auth.users u where u.id = p_user;
33
+ $$;
34
+
35
+ -- The person a Google account is linked to, if any.
36
+ CREATE FUNCTION public.be_auth_user_by_google(p_provider_id text) RETURNS uuid
37
+ LANGUAGE sql STABLE SECURITY DEFINER
38
+ SET search_path TO ''
39
+ AS $$
40
+ select i.user_id from auth.identities i where i.provider = 'google' and i.provider_id = p_provider_id limit 1;
41
+ $$;
42
+
43
+ -- Creates a person; a null hash is an invited or Google-only account that has no password yet.
44
+ CREATE FUNCTION public.be_auth_create_user(p_id uuid, p_email text, p_hash text, p_confirmed boolean, p_meta jsonb, p_now timestamp with time zone)
45
+ RETURNS uuid
46
+ LANGUAGE sql VOLATILE SECURITY DEFINER
47
+ SET search_path TO ''
48
+ AS $$
49
+ insert into auth.users (instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, invited_at,
50
+ raw_app_meta_data, raw_user_meta_data, created_at, updated_at)
51
+ values ('00000000-0000-0000-0000-000000000000', p_id, 'authenticated', 'authenticated', p_email, p_hash,
52
+ case when p_confirmed then p_now end, case when p_hash is null and not p_confirmed then p_now end,
53
+ '{"provider":"email","providers":["email"]}'::jsonb, p_meta, p_now, p_now)
54
+ returning id;
55
+ $$;
56
+
57
+ -- Links a Google account to a person.
58
+ CREATE FUNCTION public.be_auth_link_google(p_user uuid, p_provider_id text, p_data jsonb, p_now timestamp with time zone) RETURNS void
59
+ LANGUAGE sql VOLATILE SECURITY DEFINER
60
+ SET search_path TO ''
61
+ AS $$
62
+ insert into auth.identities (provider_id, user_id, identity_data, provider, last_sign_in_at, created_at, updated_at)
63
+ values (p_provider_id, p_user, p_data, 'google', p_now, p_now, p_now)
64
+ on conflict (provider_id, provider) do update set last_sign_in_at = p_now, updated_at = p_now;
65
+ $$;
66
+
67
+ -- Records a sign-in; a verified Google address also confirms the mailbox; profile keys only fill what the person has not set.
68
+ CREATE FUNCTION public.be_auth_touch_user(p_user uuid, p_now timestamp with time zone, p_confirm boolean, p_meta jsonb) RETURNS void
69
+ LANGUAGE sql VOLATILE SECURITY DEFINER
70
+ SET search_path TO ''
71
+ AS $$
72
+ update auth.users
73
+ set last_sign_in_at = p_now,
74
+ updated_at = p_now,
75
+ email_confirmed_at = case when p_confirm then coalesce(email_confirmed_at, p_now) else email_confirmed_at end,
76
+ raw_user_meta_data = p_meta || coalesce(raw_user_meta_data, '{}'::jsonb)
77
+ where id = p_user;
78
+ $$;
79
+
80
+ -- Stamps the confirmation of a mailbox (a spent confirmation link); a mailbox that is already confirmed keeps its date and the sign-in record is untouched.
81
+ CREATE FUNCTION public.be_auth_confirm_user(p_user uuid, p_now timestamp with time zone) RETURNS void
82
+ LANGUAGE sql VOLATILE SECURITY DEFINER
83
+ SET search_path TO ''
84
+ AS $$
85
+ update auth.users set email_confirmed_at = coalesce(email_confirmed_at, p_now), updated_at = p_now where id = p_user;
86
+ $$;
87
+
88
+ -- Sets a new password hash; when p_confirm is true (a spent reset link proved the mailbox) the address is confirmed too.
89
+ CREATE FUNCTION public.be_auth_set_password(p_user uuid, p_hash text, p_confirm boolean, p_now timestamp with time zone) RETURNS void
90
+ LANGUAGE sql VOLATILE SECURITY DEFINER
91
+ SET search_path TO ''
92
+ AS $$
93
+ update auth.users
94
+ set encrypted_password = p_hash,
95
+ email_confirmed_at = case when p_confirm then coalesce(email_confirmed_at, p_now) else email_confirmed_at end,
96
+ updated_at = p_now
97
+ where id = p_user;
98
+ $$;
99
+
100
+ REVOKE EXECUTE ON FUNCTION public.be_auth_user_by_email(text) FROM PUBLIC, anon, authenticated;
101
+ GRANT EXECUTE ON FUNCTION public.be_auth_user_by_email(text) TO app_be;
102
+ REVOKE EXECUTE ON FUNCTION public.be_auth_user_by_id(uuid) FROM PUBLIC, anon, authenticated;
103
+ GRANT EXECUTE ON FUNCTION public.be_auth_user_by_id(uuid) TO app_be;
104
+ REVOKE EXECUTE ON FUNCTION public.be_auth_user_by_google(text) FROM PUBLIC, anon, authenticated;
105
+ GRANT EXECUTE ON FUNCTION public.be_auth_user_by_google(text) TO app_be;
106
+ REVOKE EXECUTE ON FUNCTION public.be_auth_create_user(uuid, text, text, boolean, jsonb, timestamp with time zone) FROM PUBLIC, anon, authenticated;
107
+ GRANT EXECUTE ON FUNCTION public.be_auth_create_user(uuid, text, text, boolean, jsonb, timestamp with time zone) TO app_be;
108
+ REVOKE EXECUTE ON FUNCTION public.be_auth_link_google(uuid, text, jsonb, timestamp with time zone) FROM PUBLIC, anon, authenticated;
109
+ GRANT EXECUTE ON FUNCTION public.be_auth_link_google(uuid, text, jsonb, timestamp with time zone) TO app_be;
110
+ REVOKE EXECUTE ON FUNCTION public.be_auth_touch_user(uuid, timestamp with time zone, boolean, jsonb) FROM PUBLIC, anon, authenticated;
111
+ GRANT EXECUTE ON FUNCTION public.be_auth_touch_user(uuid, timestamp with time zone, boolean, jsonb) TO app_be;
112
+ REVOKE EXECUTE ON FUNCTION public.be_auth_confirm_user(uuid, timestamp with time zone) FROM PUBLIC, anon, authenticated;
113
+ GRANT EXECUTE ON FUNCTION public.be_auth_confirm_user(uuid, timestamp with time zone) TO app_be;
114
+ REVOKE EXECUTE ON FUNCTION public.be_auth_set_password(uuid, text, boolean, timestamp with time zone) FROM PUBLIC, anon, authenticated;
115
+ GRANT EXECUTE ON FUNCTION public.be_auth_set_password(uuid, text, boolean, timestamp with time zone) TO app_be;
@@ -92,7 +92,7 @@ jobs:
92
92
  run: npm run typecheck
93
93
  env:
94
94
  NODE_OPTIONS: {{nodeOptions}}
95
-
95
+ {{moduleTestStep}}
96
96
  lint:
97
97
  needs: plan
98
98
  if: ${{ needs.plan.outputs.checks == 'true' }}
@@ -92,7 +92,7 @@ jobs:
92
92
  run: npm run typecheck
93
93
  env:
94
94
  NODE_OPTIONS: {{nodeOptions}}
95
-
95
+ {{moduleTestStep}}
96
96
  lint:
97
97
  needs: plan
98
98
  if: ${{ needs.plan.outputs.checks == 'true' }}
@@ -1,4 +1,4 @@
1
- "build:be": "cd be && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
1
+ "build:be": "{{modulesFirst}}cd be && tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json",
2
2
  "build:fe": "{{buildFe}}",
3
3
  "codegen": "node scripts/codegen.mjs",
4
4
  "codegen:graphql": "node scripts/codegen.mjs --graphql",
@@ -4,7 +4,7 @@
4
4
  {{appScripts}}
5
5
  {{> app/package-scripts/core.json}}
6
6
  "typecheck:tests": "tsc -p be/src/tests/tsconfig.json",
7
- "test": "cd be && jest --selectProjects unit --coverage{{moduleTests}}",
7
+ "test": "{{modulesFirst}}cd be && jest --selectProjects unit --coverage",
8
8
  "test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
9
9
  "test:e2e": "npm run typecheck:tests && cd be && jest --selectProjects e2e",
10
10
  "test:contract": "npm run typecheck:tests && cd be && jest --selectProjects contract",
@@ -4,7 +4,7 @@
4
4
  {{appScripts}}
5
5
  {{> app/package-scripts/core.json}}
6
6
  "typecheck:tests": "tsc -p be/src/tests/tsconfig.json",
7
- "test": "cd be && jest --selectProjects unit --coverage{{moduleTests}}",
7
+ "test": "{{modulesFirst}}cd be && jest --selectProjects unit --coverage",
8
8
  "test:integration": "npm run typecheck:tests && cd be && jest --selectProjects integration",
9
9
  {{> app/package-scripts/db.json}}
10
10
  {{backingScripts}}
@@ -58,8 +58,11 @@ environments:
58
58
  db: 54322
59
59
  api: 3001
60
60
  web: 3000
61
+ mail: 1025
62
+ mailui: 8025
61
63
  services:
62
64
  - {name: db, wait: healthy}
65
+ - {name: mail, wait: healthy}
63
66
  database:
64
67
  service: db
65
68
  port: db
@@ -97,6 +100,11 @@ environments:
97
100
  AUTH_WEB_ORIGIN: "http://localhost:${port.web}"
98
101
  AUTH_JWT_AUDIENCE: {{project}}
99
102
  AUTH_JWT_KEY_ID: dev-1
103
+ MAIL_HOST: 127.0.0.1
104
+ MAIL_PORT: "${port.mail}"
105
+ MAIL_SECURITY: none
106
+ MAIL_FROM: no-reply@{{project}}.example.invalid
107
+ MAIL_FROM_NAME: {{project}}
100
108
  STORAGE_ENDPOINT: "http://127.0.0.1:9000"
101
109
  STORAGE_REGION: us-east-1
102
110
  STORAGE_BUCKET: {{project}}-dev
@@ -120,6 +128,7 @@ environments:
120
128
  origins:
121
129
  - {name: web, url: "http://localhost:${port.web}"}
122
130
  - {name: api, url: "http://localhost:${port.api}"}
131
+ - {name: mail, url: "http://localhost:${port.mailui}"}
123
132
  production:
124
133
  status: supported
125
134
  runtime: docker-swarm
@@ -2,7 +2,7 @@
2
2
 
3
3
  The dev environment is one command: `starci app stack up --env dev --execute`. It checks the machine, opens the dev custody, runs the setup
4
4
  commands, starts the dependencies in Docker Linux containers (the `db` service: the official postgres image plus pgvector, built from
5
- `.starcistacks/images/db/Dockerfile`), gives the environment its own database role, migrates, runs the seed, starts the api and web
5
+ `.starcistacks/images/db/Dockerfile`; the `mail` service: Mailpit, the dev mail sink), gives the environment its own database role, migrates, runs the seed, starts the api and web
6
6
  processes natively and waits for readiness. What it runs is the `local` block of `environments.dev` in `.starcistacks/application-stacks.yaml`
7
7
  (PROJECT-STACKS-6 in contexts/project/stacks.md); this runbook never carries a docker command, an agent never runs docker by hand.
8
8
 
@@ -26,5 +26,7 @@ another environment.
26
26
  | down | `starci app stack down --env dev --execute`: ends the processes and stops the compose project; volumes and sealed values stay. |
27
27
  | verification | `starci app stack verify --env dev`: the declared liveness and readiness probes after a real start, then `npm run lint` from the app root. |
28
28
 
29
+ **Mail.** The confirmation and reset mails of the sign-in go to the `mail` service on its SMTP port (`MAIL_HOST`, `MAIL_PORT`, `MAIL_SECURITY: none`: a plain channel that only the loopback sink accepts) and nothing leaves the machine. Open the web UI at the `mail` origin `up` prints (`http://localhost:8025` by default), sign up in the web app and follow the link from the message there. A deployed environment names a real SMTP server and `MAIL_SECURITY: starttls` or `tls`.
30
+
29
31
  The database volume `db-data` is rebuildable development data: `starci app db migrate` recreates its schema from `db/bootstrap.sql` and
30
32
  `db/migrations`. Key names of the environment are in `.starcistacks/dev/runtime/env/KEYS.md`.
@@ -1,6 +1,8 @@
1
1
  # The dev environment's dependencies, run by `starci app stack up --env dev --execute` (PROJECT-STACKS-3, PROJECT-STACKS-6).
2
2
  # Host ports come from environments.dev.local.ports through STACK_PORT_<NAME>; the superuser password is read from a file through the least
3
- # grant STACK_GRANT_DB (a directory holding only POSTGRES_PASSWORD), never from the environment. Every port publishes on loopback only. The api and web run natively against these containers.
3
+ # grant STACK_GRANT_DB (a directory holding only POSTGRES_PASSWORD), never from the environment. Every port publishes on loopback only.
4
+ # `mail` is the dev mail sink (Mailpit, an unmodified upstream image pinned by digest in contexts/stack/pins.yaml): the api sends the
5
+ # confirmation and reset mails to its SMTP port, over a plain channel that exists on loopback only, and a person reads them in its web UI. The api and web run natively against these containers.
4
6
  name: {{project}}-dev
5
7
  services:
6
8
  db:
@@ -21,5 +23,21 @@ services:
21
23
  retries: 30
22
24
  start_period: 10s
23
25
  restart: unless-stopped
26
+ mail:
27
+ image: axllent/mailpit:v1.31.6@sha256:23fe39e81bde596a918000b3135c0f85dc8bb4c9ea6be274b051143f3e300387
28
+ environment:
29
+ MP_SMTP_BIND_ADDR: 0.0.0.0:1025
30
+ MP_UI_BIND_ADDR: 0.0.0.0:8025
31
+ MP_MAX_MESSAGES: "500"
32
+ ports:
33
+ - "127.0.0.1:${STACK_PORT_MAIL}:1025"
34
+ - "127.0.0.1:${STACK_PORT_MAILUI}:8025"
35
+ healthcheck:
36
+ test: ["CMD", "/mailpit", "readyz"]
37
+ interval: 5s
38
+ timeout: 5s
39
+ retries: 20
40
+ start_period: 5s
41
+ restart: unless-stopped
24
42
  volumes:
25
43
  db-data: {}
@@ -18,5 +18,8 @@ Names only. Dev values are generated once by the owner-armed `starci app secret
18
18
  | `AUTH_JWT_PRIVATE_KEY` | API Ed25519 (EdDSA) signing key (member `auth`) |
19
19
  | `STORAGE_ENDPOINT`, `STORAGE_REGION`, `STORAGE_BUCKET` | API storage address |
20
20
  | `STORAGE_ACCESS_KEY_ID`, `STORAGE_SECRET_ACCESS_KEY` | API storage credentials (member `storage`) |
21
+ | `MAIL_HOST`, `MAIL_PORT`, `MAIL_SECURITY`, `MAIL_FROM`, `MAIL_FROM_NAME` | API outgoing mail server (the dev `mail` service, Mailpit, on its SMTP port: a plain channel on loopback only) and the sender of the confirmation and reset mails |
22
+ | `MAIL_DEFAULT_LANGUAGE`, `MAIL_CONFIRM_TTL_SECONDS`, `MAIL_RESET_TTL_SECONDS` | API mail language (`vi` or `en`) and link lifetimes; optional |
23
+ | `MAIL_USER`, `MAIL_PASSWORD` | API mail server login; optional, the password reads `MAIL_PASSWORD_FILE` |
21
24
  | `NEXT_PUBLIC_SITE_URL`, `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_GRAPHQL_URL` | Front-end site, auth doors and GraphQL transport |
22
25
  | `DATABASE_URL` | `starci app db` verbs; the stack verb composes it in memory for the migration |
@@ -17,6 +17,11 @@ services:
17
17
  AUTH_JWT_KEY_ID: production-1
18
18
  AUTH_COOKIE_DOMAIN: {{project}}.example.invalid
19
19
  AUTH_JWT_PRIVATE_KEY_FILE: /run/secrets/auth-jwt-private-key
20
+ MAIL_HOST: smtp.{{project}}.example.invalid
21
+ MAIL_PORT: "587"
22
+ MAIL_SECURITY: starttls
23
+ MAIL_FROM: no-reply@{{project}}.example.invalid
24
+ MAIL_FROM_NAME: {{project}}
20
25
  STORAGE_ENDPOINT: https://storage.{{project}}.example.invalid
21
26
  STORAGE_REGION: us-east-1
22
27
  STORAGE_BUCKET: {{project}}-production
@@ -23,6 +23,10 @@ This inventory declares names, consumers and mounted file paths. Scaffolding gen
23
23
  | `STORAGE_BUCKET` | API storage bucket |
24
24
  | `STORAGE_ACCESS_KEY_ID` | API storage credential; reads `STORAGE_ACCESS_KEY_ID_FILE` at `/run/secrets/storage-access-key-id` |
25
25
  | `STORAGE_SECRET_ACCESS_KEY` | API storage credential; reads `STORAGE_SECRET_ACCESS_KEY_FILE` at `/run/secrets/storage-secret-access-key` |
26
+ | `MAIL_HOST`, `MAIL_PORT`, `MAIL_SECURITY` | API outgoing mail server and how its channel is secured (`starttls` is required to succeed, `tls` is implicit TLS) |
27
+ | `MAIL_FROM`, `MAIL_FROM_NAME` | API sender address and name of the confirmation and reset mails |
28
+ | `MAIL_DEFAULT_LANGUAGE`, `MAIL_CONFIRM_TTL_SECONDS`, `MAIL_RESET_TTL_SECONDS` | API mail language (`vi` or `en`) and link lifetimes; optional |
29
+ | `MAIL_USER`, `MAIL_PASSWORD` | API mail server login; optional, the password reads `MAIL_PASSWORD_FILE` at `/run/secrets/mail-password` |
26
30
  | `DATABASE_URL` | `starci app db` verbs (migrate, dump) and `contract:emit`, from the operator's environment |
27
31
  | `NEXT_PUBLIC_SITE_URL` | Front-end site URL |
28
32
  | `NEXT_PUBLIC_API_URL` | Front-end auth doors of the API |
@@ -24,10 +24,6 @@ begin
24
24
  end;
25
25
  $$;
26
26
 
27
- grant usage on schema auth to app_be;
28
- grant select, insert, update on table auth.users to app_be;
29
- grant select, insert on table auth.identities to app_be;
30
-
31
27
  -- The device sessions and rotating refresh tokens of @starci/auth. A token is stored by its sha256 only; a rotated token stays,
32
28
  -- marked used, so a replay is detected (a replay inside the grace window is a racing tab, an older one ends the session).
33
29
  -- identity_attempts counts the sign-in attempts of one address in a window every replica shares (the budget of failures).
@@ -87,6 +83,30 @@ create policy identity_refresh_tokens_app_be_all on public.identity_refresh_toke
87
83
  create policy identity_attempts_app_be_all on public.identity_attempts
88
84
  for all to app_be using (current_user = 'app_be') with check (current_user = 'app_be');
89
85
 
86
+ {{> app/auth-doors.sql.partial}}
87
+
88
+ -- The single-use links that confirm a mailbox or reset a password (the email doors of sign-in): only the sha256 of the token is
89
+ -- stored, a spent link stays marked used, and a new link of the same purpose ends the older ones.
90
+ create table public.identity_email_tokens (
91
+ id uuid primary key,
92
+ user_id uuid not null references auth.users (id) on delete cascade,
93
+ purpose varchar(16) not null check (purpose in ('confirm', 'reset')),
94
+ token_hash varchar(64) not null,
95
+ expires_at timestamptz not null,
96
+ used_at timestamptz,
97
+ created_at timestamptz not null,
98
+ constraint identity_email_tokens_hash_key unique (token_hash)
99
+ );
100
+
101
+ create index identity_email_tokens_user_idx on public.identity_email_tokens (user_id, purpose);
102
+
103
+ alter table public.identity_email_tokens enable row level security;
104
+ alter table public.identity_email_tokens force row level security;
105
+ revoke all on table public.identity_email_tokens from public;
106
+ grant select, insert, update, delete on table public.identity_email_tokens to app_be;
107
+ create policy identity_email_tokens_app_be_all on public.identity_email_tokens
108
+ for all to app_be using (current_user = 'app_be') with check (current_user = 'app_be');
109
+
90
110
  create table public.profiles (
91
111
  id uuid primary key references auth.users (id) on delete cascade,
92
112
  display_name text not null check (char_length(display_name) between 1 and 120),
@@ -1,4 +1,5 @@
1
1
  // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import type { MailLanguage } from "@modules/integrations/mail"
2
3
  import type { HttpExchange } from "@modules/integrations/passport"
3
4
 
4
5
  /** One cookie door of sign-in: the request and response the strategy or the refresh cookie work on. */
@@ -9,3 +10,35 @@ export interface ExchangeRequest {
9
10
 
10
11
  /** A request with nothing in it. */
11
12
  export type EmptyRequest = Record<string, never>
13
+
14
+ /** The posted data of the signup door. */
15
+ export interface SignupData {
16
+ /** The sign-in address. */
17
+ readonly email: string
18
+ /** The chosen password. */
19
+ readonly password: string
20
+ /** The language of the confirmation mail, when the request names one. */
21
+ readonly language: MailLanguage | undefined
22
+ }
23
+
24
+ /** The posted data of the resend and forgot doors. */
25
+ export interface AddressData {
26
+ /** The address the mail goes to. */
27
+ readonly email: string
28
+ /** The language of the mail, when the request names one. */
29
+ readonly language: MailLanguage | undefined
30
+ }
31
+
32
+ /** The link token of the confirm door. */
33
+ export interface TokenData {
34
+ /** The token the link carries. */
35
+ readonly token: string
36
+ }
37
+
38
+ /** The posted data of the reset door. */
39
+ export interface ResetData {
40
+ /** The token the link carries. */
41
+ readonly token: string
42
+ /** The new password. */
43
+ readonly password: string
44
+ }
@@ -0,0 +1,11 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { Command } from "@nestjs/cqrs"
3
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
4
+ import type { TokenData } from "./auth.contracts"
5
+
6
+ /** Spends a confirmation link and confirms the mailbox. */
7
+ export class ConfirmCommand extends Command<void> {
8
+ constructor(readonly params: PublicExecuteParams<TokenData>) {
9
+ super()
10
+ }
11
+ }
@@ -0,0 +1,22 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { CommandHandler } from "@nestjs/cqrs"
3
+ import { IdentityEmailService } from "@modules/domain/identity"
4
+ import { ICQRSHandler } from "@modules/platform/cqrs"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { ConfirmCommand } from "./confirm.command"
8
+
9
+ @CommandHandler(ConfirmCommand)
10
+ /** Spends a confirmation link and confirms the mailbox. */
11
+ export class ConfirmHandler extends ICQRSHandler<ConfirmCommand, void> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ private readonly email: IdentityEmailService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(command: ConfirmCommand): Promise<void> {
20
+ return this.email.confirm(command.params.request.token)
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { Command } from "@nestjs/cqrs"
3
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
4
+ import type { AddressData } from "./auth.contracts"
5
+
6
+ /** Mails a password reset link when the address has an account; the answer never says whether it does. */
7
+ export class ForgotCommand extends Command<void> {
8
+ constructor(readonly params: PublicExecuteParams<AddressData>) {
9
+ super()
10
+ }
11
+ }
@@ -0,0 +1,22 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { CommandHandler } from "@nestjs/cqrs"
3
+ import { IdentityEmailService } from "@modules/domain/identity"
4
+ import { ICQRSHandler } from "@modules/platform/cqrs"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { ForgotCommand } from "./forgot.command"
8
+
9
+ @CommandHandler(ForgotCommand)
10
+ /** Mails a password reset link when the address has an account; the answer never says whether it does. */
11
+ export class ForgotHandler extends ICQRSHandler<ForgotCommand, void> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ private readonly email: IdentityEmailService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(command: ForgotCommand): Promise<void> {
20
+ return this.email.forgot(command.params.request.email, command.params.request.language)
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { Command } from "@nestjs/cqrs"
3
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
4
+ import type { AddressData } from "./auth.contracts"
5
+
6
+ /** Mails a new confirmation link to an unconfirmed account; the answer never says whether the address has one. */
7
+ export class ResendCommand extends Command<void> {
8
+ constructor(readonly params: PublicExecuteParams<AddressData>) {
9
+ super()
10
+ }
11
+ }
@@ -0,0 +1,22 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { CommandHandler } from "@nestjs/cqrs"
3
+ import { IdentityEmailService } from "@modules/domain/identity"
4
+ import { ICQRSHandler } from "@modules/platform/cqrs"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { ResendCommand } from "./resend.command"
8
+
9
+ @CommandHandler(ResendCommand)
10
+ /** Mails a new confirmation link to an unconfirmed account; the answer never says whether the address has one. */
11
+ export class ResendHandler extends ICQRSHandler<ResendCommand, void> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ private readonly email: IdentityEmailService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(command: ResendCommand): Promise<void> {
20
+ return this.email.resend(command.params.request.email, command.params.request.language)
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { Command } from "@nestjs/cqrs"
3
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
4
+ import type { ResetData } from "./auth.contracts"
5
+
6
+ /** Spends a reset link: sets the new password and ends every session of the person. */
7
+ export class ResetCommand extends Command<void> {
8
+ constructor(readonly params: PublicExecuteParams<ResetData>) {
9
+ super()
10
+ }
11
+ }
@@ -0,0 +1,22 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { CommandHandler } from "@nestjs/cqrs"
3
+ import { IdentityEmailService } from "@modules/domain/identity"
4
+ import { ICQRSHandler } from "@modules/platform/cqrs"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { ResetCommand } from "./reset.command"
8
+
9
+ @CommandHandler(ResetCommand)
10
+ /** Spends a reset link: sets the new password and ends every session of the person. */
11
+ export class ResetHandler extends ICQRSHandler<ResetCommand, void> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ private readonly email: IdentityEmailService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(command: ResetCommand): Promise<void> {
20
+ return this.email.reset(command.params.request.token, command.params.request.password)
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { Command } from "@nestjs/cqrs"
3
+ import type { PublicExecuteParams } from "@modules/platform/cqrs"
4
+ import type { SignupData } from "./auth.contracts"
5
+
6
+ /** Creates an unconfirmed password account and mails the confirmation link; the answer never says whether the address already had an account. */
7
+ export class SignupCommand extends Command<void> {
8
+ constructor(readonly params: PublicExecuteParams<SignupData>) {
9
+ super()
10
+ }
11
+ }
@@ -0,0 +1,22 @@
1
+ // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
+ import { CommandHandler } from "@nestjs/cqrs"
3
+ import { IdentityEmailService } from "@modules/domain/identity"
4
+ import { ICQRSHandler } from "@modules/platform/cqrs"
5
+ import { InjectLogger } from "@modules/platform/logging"
6
+ import type { Logger } from "@modules/platform/logging"
7
+ import { SignupCommand } from "./signup.command"
8
+
9
+ @CommandHandler(SignupCommand)
10
+ /** Creates an unconfirmed password account and mails the confirmation link; the answer never says whether the address already had an account. */
11
+ export class SignupHandler extends ICQRSHandler<SignupCommand, void> {
12
+ constructor(
13
+ @InjectLogger() logger: Logger,
14
+ private readonly email: IdentityEmailService,
15
+ ) {
16
+ super(logger)
17
+ }
18
+
19
+ protected override process(command: SignupCommand): Promise<void> {
20
+ return this.email.signup(command.params.request.email, command.params.request.password, command.params.request.language)
21
+ }
22
+ }
@@ -1,13 +1,6 @@
1
- // Managed by starci app sync (slot be.feature.auth); do not edit: run "npx starci app sync --execute".
2
- import { ConfigurableModuleBuilder, type ConfigurableModuleHost } from "@nestjs/common"
1
+ import { defineConfigurableModule } from "@starci/nest-kit"
3
2
  import type { AuthApiOptions } from "./auth.options"
4
3
 
5
- const built = new ConfigurableModuleBuilder<AuthApiOptions>().setClassMethodName("register").build()
6
- type Host = ConfigurableModuleHost<AuthApiOptions, "register", "create", {}>
7
-
8
4
  /** The configurable-module base of the auth feature module. */
9
- export const ConfigurableModuleClass: Host["ConfigurableModuleClass"] = built.ConfigurableModuleClass
10
- /** The injection token of the registered options. */
11
- export const MODULE_OPTIONS_TOKEN: Host["MODULE_OPTIONS_TOKEN"] = built.MODULE_OPTIONS_TOKEN
12
- /** The options of a synchronous registration, for the register signature. */
13
- export const OPTIONS_TYPE: Host["OPTIONS_TYPE"] = built.OPTIONS_TYPE
5
+ export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN, OPTIONS_TYPE } =
6
+ defineConfigurableModule<AuthApiOptions>()