@starci/hfs 5.0.8 → 5.2.0

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