create-nexwire 4.2.5 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (399) hide show
  1. package/README.md +29 -12
  2. package/express/.env +1 -1
  3. package/express/package.json +1 -1
  4. package/express/src/config/database.ts +48 -29
  5. package/express/src/config/index.ts +83 -83
  6. package/express/src/config/openapi.ts +6 -24
  7. package/express/src/config/queue.ts +30 -30
  8. package/express/src/config/validate.ts +50 -40
  9. package/express/src/database/migrations/sqlite/0000_init.sql +47 -47
  10. package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  11. package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
  12. package/express/src/framework/cache/cache.ts +40 -8
  13. package/express/src/framework/circuit-breaker/cb.ts +88 -60
  14. package/express/src/framework/database/connection.ts +52 -27
  15. package/express/src/framework/events/dispatcher.ts +61 -49
  16. package/express/src/framework/facade.ts +47 -3
  17. package/express/src/framework/http/app.ts +28 -22
  18. package/express/src/framework/http/logger.ts +37 -17
  19. package/express/src/framework/http/openapi.ts +9 -11
  20. package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
  21. package/express/src/framework/modules/routes.ts +26 -21
  22. package/express/src/framework/notification/channels/broadcast.ts +15 -0
  23. package/express/src/framework/notification/channels/mail.ts +42 -0
  24. package/express/src/framework/notification/contracts.ts +29 -0
  25. package/express/src/framework/notification/index.ts +19 -94
  26. package/express/src/framework/notification/registry.ts +15 -0
  27. package/express/src/framework/providers/adapter.ts +27 -17
  28. package/express/src/framework/queue/queue.ts +43 -44
  29. package/express/src/framework/realtime/broadcast.ts +29 -5
  30. package/express/src/framework/realtime/socket.ts +149 -119
  31. package/express/src/framework/redis/client.ts +148 -151
  32. package/express/src/framework/runtime/server-adapter.ts +39 -34
  33. package/express/src/framework/scheduler/lock.ts +128 -116
  34. package/express/src/framework/server.ts +33 -49
  35. package/express/src/framework/session/session.ts +69 -14
  36. package/express/src/framework/storage/storage.ts +744 -785
  37. package/express/src/framework/support/mail.ts +15 -7
  38. package/express/src/middlewares/auth-middleware.ts +1 -2
  39. package/express/src/middlewares/role-middleware.ts +3 -13
  40. package/express/src/modules/auth/routes/index.ts +1 -2
  41. package/express/src/providers/notification.ts +4 -0
  42. package/express/src/resources/src/components/Switch.vue +60 -65
  43. package/express/src/resources/src/layouts/Layout/Header.vue +124 -105
  44. package/express/src/resources/src/pages/auth/forgetPassword.vue +23 -30
  45. package/express/src/resources/src/pages/auth/login.vue +25 -28
  46. package/express/src/resources/src/pages/auth/register.vue +35 -39
  47. package/express/src/resources/src/pages/auth/resetPassword.vue +29 -37
  48. package/express/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  49. package/express/src/resources/src/plugins/gum.ts +519 -203
  50. package/express/src/resources/src/plugins/pulse.ts +157 -147
  51. package/express/src/resources/src/stores/auth.ts +9 -6
  52. package/express/src/resources/vite.config.ts +54 -43
  53. package/express/src/storage/database/nexwire.sqlite +0 -0
  54. package/express/src/storage/logs/app.log +0 -0
  55. package/express/src/storage/logs/fatal.log +0 -0
  56. package/express/src/types/global.d.ts +32 -26
  57. package/fastify/.dockerignore +14 -0
  58. package/fastify/.env +44 -0
  59. package/fastify/.env.example +42 -0
  60. package/fastify/.vscode/extensions.json +6 -0
  61. package/fastify/.vscode/settings.json +37 -0
  62. package/fastify/LICENSE +21 -0
  63. package/fastify/README.md +232 -0
  64. package/fastify/biome.json +99 -0
  65. package/fastify/drizzle.config.ts +25 -0
  66. package/fastify/gitignore-stub +31 -0
  67. package/fastify/package.json +130 -0
  68. package/fastify/public/.gitkeep +0 -0
  69. package/fastify/src/config/app.ts +41 -0
  70. package/fastify/src/config/auth.ts +12 -0
  71. package/fastify/src/config/cache.ts +16 -0
  72. package/fastify/src/config/cookie.ts +15 -0
  73. package/fastify/src/config/cors.ts +12 -0
  74. package/fastify/src/config/database.ts +48 -0
  75. package/fastify/src/config/index.ts +83 -0
  76. package/fastify/src/config/jwt.ts +19 -0
  77. package/fastify/src/config/logging.ts +13 -0
  78. package/fastify/src/config/mail.ts +24 -0
  79. package/fastify/src/config/openapi.ts +36 -0
  80. package/fastify/src/config/queue.ts +30 -0
  81. package/fastify/src/config/rateLimit.ts +17 -0
  82. package/fastify/src/config/realtime.ts +15 -0
  83. package/fastify/src/config/redis.ts +18 -0
  84. package/fastify/src/config/security.ts +44 -0
  85. package/fastify/src/config/session.ts +17 -0
  86. package/fastify/src/config/storage.ts +22 -0
  87. package/fastify/src/config/validate.ts +50 -0
  88. package/fastify/src/database/migrations/sqlite/0000_init.sql +48 -0
  89. package/fastify/src/database/migrations/sqlite/meta/0000_snapshot.json +324 -0
  90. package/fastify/src/database/migrations/sqlite/meta/_journal.json +13 -0
  91. package/fastify/src/database/schema.ts +2 -0
  92. package/fastify/src/env.ts +76 -0
  93. package/fastify/src/framework/cache/cache.ts +114 -0
  94. package/fastify/src/framework/circuit-breaker/cb.ts +88 -0
  95. package/fastify/src/framework/database/app-schema.ts +37 -0
  96. package/fastify/src/framework/database/connection.ts +223 -0
  97. package/fastify/src/framework/database/migrate-hooks.ts +87 -0
  98. package/fastify/src/framework/database/paginate.ts +226 -0
  99. package/fastify/src/framework/database/schema.ts +20 -0
  100. package/fastify/src/framework/database/seed.ts +152 -0
  101. package/fastify/src/framework/events/dispatcher.ts +61 -0
  102. package/fastify/src/framework/facade.ts +96 -0
  103. package/fastify/src/framework/http/app.ts +176 -0
  104. package/fastify/src/framework/http/cors.ts +13 -0
  105. package/fastify/src/framework/http/fastify.d.ts +13 -0
  106. package/fastify/src/framework/http/logger.ts +110 -0
  107. package/fastify/src/framework/http/metrics.ts +22 -0
  108. package/fastify/src/framework/http/openapi.ts +48 -0
  109. package/fastify/src/framework/http/ratelimiter.ts +47 -0
  110. package/fastify/src/framework/http/security.ts +25 -0
  111. package/fastify/src/framework/http/static.ts +83 -0
  112. package/fastify/src/framework/http/validation.ts +49 -0
  113. package/fastify/src/framework/kernel.ts +43 -0
  114. package/fastify/src/framework/maker-cli/db/core.mjs +291 -0
  115. package/fastify/src/framework/maker-cli/db/index.mjs +204 -0
  116. package/fastify/src/framework/maker-cli/deploy/core.mjs +1046 -0
  117. package/fastify/src/framework/maker-cli/deploy/index.mjs +91 -0
  118. package/fastify/src/framework/maker-cli/index.mjs +65 -0
  119. package/fastify/src/framework/maker-cli/middleware/core.mjs +41 -0
  120. package/fastify/src/framework/maker-cli/middleware/index.mjs +17 -0
  121. package/fastify/src/framework/maker-cli/module/core.mjs +1066 -0
  122. package/fastify/src/framework/maker-cli/module/index.mjs +211 -0
  123. package/fastify/src/framework/maker-cli/provider/core.mjs +49 -0
  124. package/fastify/src/framework/maker-cli/provider/index.mjs +12 -0
  125. package/fastify/src/framework/maker-cli/runtime/build-ui.mjs +16 -0
  126. package/fastify/src/framework/maker-cli/runtime/core.mjs +560 -0
  127. package/fastify/src/framework/maker-cli/runtime/index.mjs +120 -0
  128. package/fastify/src/framework/maker-cli/runtime/redis-commander-patch.cjs +20 -0
  129. package/fastify/src/framework/maker-cli/stubs/controller/openapi.ts.stub +74 -0
  130. package/fastify/src/framework/maker-cli/stubs/controller/plain.ts.stub +77 -0
  131. package/fastify/src/framework/maker-cli/stubs/controller/standalone.ts.stub +51 -0
  132. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +52 -0
  133. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +55 -0
  134. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +51 -0
  135. package/fastify/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +55 -0
  136. package/fastify/src/framework/maker-cli/stubs/deploy/README.stub +59 -0
  137. package/fastify/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +45 -0
  138. package/fastify/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +45 -0
  139. package/fastify/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +45 -0
  140. package/fastify/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +68 -0
  141. package/fastify/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +68 -0
  142. package/fastify/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +67 -0
  143. package/fastify/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +41 -0
  144. package/fastify/src/framework/maker-cli/stubs/deploy/server/README.stub +78 -0
  145. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +120 -0
  146. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +133 -0
  147. package/fastify/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +131 -0
  148. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +11 -0
  149. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +25 -0
  150. package/fastify/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +25 -0
  151. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +16 -0
  152. package/fastify/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +13 -0
  153. package/fastify/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +14 -0
  154. package/fastify/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +7 -0
  155. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +54 -0
  156. package/fastify/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +70 -0
  157. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +25 -0
  158. package/fastify/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +24 -0
  159. package/fastify/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
  160. package/fastify/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
  161. package/fastify/src/framework/maker-cli/stubs/job/name.ts.stub +25 -0
  162. package/fastify/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
  163. package/fastify/src/framework/maker-cli/stubs/middleware/name.ts.stub +15 -0
  164. package/fastify/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +10 -0
  165. package/fastify/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +10 -0
  166. package/fastify/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +10 -0
  167. package/fastify/src/framework/maker-cli/stubs/notification/controller.ts.stub +79 -0
  168. package/fastify/src/framework/maker-cli/stubs/notification/job.ts.stub +11 -0
  169. package/fastify/src/framework/maker-cli/stubs/notification/model.ts.stub +29 -0
  170. package/fastify/src/framework/maker-cli/stubs/notification/provider.ts.stub +174 -0
  171. package/fastify/src/framework/maker-cli/stubs/notification/route.api.ts.stub +71 -0
  172. package/fastify/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +15 -0
  173. package/fastify/src/framework/maker-cli/stubs/notification/schema.plain.ts.stub +26 -0
  174. package/fastify/src/framework/maker-cli/stubs/notification/schema.ts.stub +26 -0
  175. package/fastify/src/framework/maker-cli/stubs/provider/module-barrel.ts.stub +19 -0
  176. package/fastify/src/framework/maker-cli/stubs/provider/module.ts.stub +21 -0
  177. package/fastify/src/framework/maker-cli/stubs/provider/name.ts.stub +21 -0
  178. package/fastify/src/framework/maker-cli/stubs/route/api.ts.stub +101 -0
  179. package/fastify/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -0
  180. package/fastify/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +120 -0
  181. package/fastify/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +50 -0
  182. package/fastify/src/framework/maker-cli/stubs/schedule/name.ts.stub +36 -0
  183. package/fastify/src/framework/maker-cli/stubs/schema/name.plain.ts.stub +14 -0
  184. package/fastify/src/framework/maker-cli/stubs/schema/name.ts.stub +33 -0
  185. package/fastify/src/framework/maker-cli/stubs/seeder/name.ts.stub +18 -0
  186. package/fastify/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
  187. package/fastify/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
  188. package/fastify/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
  189. package/fastify/src/framework/maker-cli/stubs/test/unit.ts.stub +22 -0
  190. package/fastify/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
  191. package/fastify/src/framework/maker-cli/utils/env-config.mjs +44 -0
  192. package/fastify/src/framework/maker-cli/utils/env-db.mjs +61 -0
  193. package/fastify/src/framework/maker-cli/utils/file-ops.mjs +30 -0
  194. package/fastify/src/framework/maker-cli/utils/flags.mjs +14 -0
  195. package/fastify/src/framework/maker-cli/utils/help.mjs +379 -0
  196. package/fastify/src/framework/maker-cli/utils/naming.mjs +119 -0
  197. package/fastify/src/framework/maker-cli/utils/process.mjs +52 -0
  198. package/fastify/src/framework/maker-cli/utils/project-commands.mjs +201 -0
  199. package/fastify/src/framework/modules/discover.ts +57 -0
  200. package/fastify/src/framework/modules/routes.ts +31 -0
  201. package/fastify/src/framework/notification/channels/broadcast.ts +15 -0
  202. package/fastify/src/framework/notification/channels/mail.ts +42 -0
  203. package/fastify/src/framework/notification/contracts.ts +29 -0
  204. package/fastify/src/framework/notification/index.ts +59 -0
  205. package/fastify/src/framework/notification/registry.ts +15 -0
  206. package/fastify/src/framework/providers/adapter.ts +149 -0
  207. package/fastify/src/framework/providers/contracts.ts +126 -0
  208. package/fastify/src/framework/providers/loader.ts +105 -0
  209. package/fastify/src/framework/providers/registry.ts +88 -0
  210. package/fastify/src/framework/queue/clear.ts +22 -0
  211. package/fastify/src/framework/queue/queue.ts +401 -0
  212. package/fastify/src/framework/queue/ui.ts +194 -0
  213. package/fastify/src/framework/queue/worker.ts +61 -0
  214. package/fastify/src/framework/realtime/broadcast.ts +70 -0
  215. package/fastify/src/framework/realtime/index.ts +2 -0
  216. package/fastify/src/framework/realtime/socket-cookie.ts +72 -0
  217. package/fastify/src/framework/realtime/socket.ts +149 -0
  218. package/fastify/src/framework/realtime/types.ts +6 -0
  219. package/fastify/src/framework/realtime/ui.ts +16 -0
  220. package/fastify/src/framework/redis/client.ts +148 -0
  221. package/fastify/src/framework/runtime/runtime.ts +23 -0
  222. package/fastify/src/framework/runtime/server-adapter.ts +33 -0
  223. package/fastify/src/framework/scheduler/lock.ts +128 -0
  224. package/fastify/src/framework/scheduler/run.ts +48 -0
  225. package/fastify/src/framework/scheduler/scheduler.ts +155 -0
  226. package/fastify/src/framework/server.ts +162 -0
  227. package/fastify/src/framework/session/session.ts +160 -0
  228. package/fastify/src/framework/storage/storage.ts +744 -0
  229. package/fastify/src/framework/support/cookie.ts +71 -0
  230. package/fastify/src/framework/support/jwt.ts +33 -0
  231. package/fastify/src/framework/support/lifecycle.ts +35 -0
  232. package/fastify/src/framework/support/logger.ts +148 -0
  233. package/fastify/src/framework/support/mail.ts +56 -0
  234. package/fastify/src/framework/support/password.ts +23 -0
  235. package/fastify/src/framework/support/status-codes.ts +75 -0
  236. package/fastify/src/framework/support/upload.ts +181 -0
  237. package/fastify/src/framework/support/url.ts +41 -0
  238. package/fastify/src/middlewares/auth-middleware.ts +134 -0
  239. package/fastify/src/middlewares/role-middleware.ts +19 -0
  240. package/fastify/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
  241. package/fastify/src/modules/auth/__tests__/login.test.ts +171 -0
  242. package/fastify/src/modules/auth/__tests__/logout.test.ts +60 -0
  243. package/fastify/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
  244. package/fastify/src/modules/auth/__tests__/me.test.ts +91 -0
  245. package/fastify/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
  246. package/fastify/src/modules/auth/__tests__/register.test.ts +291 -0
  247. package/fastify/src/modules/auth/__tests__/resetpass.test.ts +206 -0
  248. package/fastify/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
  249. package/fastify/src/modules/auth/controllers/auth.ts +252 -0
  250. package/fastify/src/modules/auth/controllers/role.ts +35 -0
  251. package/fastify/src/modules/auth/database/models/role.ts +14 -0
  252. package/fastify/src/modules/auth/database/models/user.ts +46 -0
  253. package/fastify/src/modules/auth/database/seeders/role.ts +18 -0
  254. package/fastify/src/modules/auth/database/seeders/user.ts +35 -0
  255. package/fastify/src/modules/auth/facade.ts +12 -0
  256. package/fastify/src/modules/auth/helpers/auth.ts +115 -0
  257. package/fastify/src/modules/auth/jobs/forgetpass.ts +20 -0
  258. package/fastify/src/modules/auth/jobs/registeruser.ts +23 -0
  259. package/fastify/src/modules/auth/jobs/verifyemail.ts +18 -0
  260. package/fastify/src/modules/auth/routes/index.ts +185 -0
  261. package/fastify/src/modules/auth/routes/role.ts +56 -0
  262. package/fastify/src/modules/auth/schemas/auth.ts +116 -0
  263. package/fastify/src/modules/auth/services/forgetpassword.ts +66 -0
  264. package/fastify/src/modules/auth/services/login.ts +63 -0
  265. package/fastify/src/modules/auth/services/logout.ts +25 -0
  266. package/fastify/src/modules/auth/services/logoutalldevices.ts +19 -0
  267. package/fastify/src/modules/auth/services/me.ts +31 -0
  268. package/fastify/src/modules/auth/services/refreshtoken.ts +110 -0
  269. package/fastify/src/modules/auth/services/register.ts +141 -0
  270. package/fastify/src/modules/auth/services/resetpassword.ts +69 -0
  271. package/fastify/src/modules/auth/services/verifyemail.ts +77 -0
  272. package/fastify/src/modules/auth/types/auth.ts +31 -0
  273. package/fastify/src/providers/README.md +107 -0
  274. package/fastify/src/providers/notification.ts +4 -0
  275. package/fastify/src/resources/index.html +17 -0
  276. package/fastify/src/resources/src/App.vue +5 -0
  277. package/fastify/src/resources/src/assets/css/styles.css +14761 -0
  278. package/fastify/src/resources/src/assets/css/styles.css.map +1 -0
  279. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin-ext.woff2 +0 -0
  280. package/fastify/src/resources/src/assets/fonts/public-sans/public-sans-latin.woff2 +0 -0
  281. package/fastify/src/resources/src/assets/images/favicon/favicon.ico +0 -0
  282. package/fastify/src/resources/src/assets/images/logo.png +0 -0
  283. package/fastify/src/resources/src/assets/scss/custom.css +217 -0
  284. package/fastify/src/resources/src/assets/scss/custom.css.map +1 -0
  285. package/fastify/src/resources/src/assets/scss/custom.scss +1346 -0
  286. package/fastify/src/resources/src/components/Button.vue +32 -0
  287. package/fastify/src/resources/src/components/Checkbox.vue +32 -0
  288. package/fastify/src/resources/src/components/Datepicker.vue +106 -0
  289. package/fastify/src/resources/src/components/FeatureButton.vue +53 -0
  290. package/fastify/src/resources/src/components/FloatButton.vue +33 -0
  291. package/fastify/src/resources/src/components/Href.vue +32 -0
  292. package/fastify/src/resources/src/components/Input.vue +243 -0
  293. package/fastify/src/resources/src/components/InputGroup.vue +147 -0
  294. package/fastify/src/resources/src/components/InputPasswordToggle.vue +229 -0
  295. package/fastify/src/resources/src/components/Modal.vue +71 -0
  296. package/fastify/src/resources/src/components/Pagebar.vue +26 -0
  297. package/fastify/src/resources/src/components/Refresh.vue +19 -0
  298. package/fastify/src/resources/src/components/Select.vue +384 -0
  299. package/fastify/src/resources/src/components/Spinner.vue +42 -0
  300. package/fastify/src/resources/src/components/Switch.vue +60 -0
  301. package/fastify/src/resources/src/components/TextArea.vue +117 -0
  302. package/fastify/src/resources/src/components/Toast.vue +56 -0
  303. package/fastify/src/resources/src/components/datatable/DataTableSkeleton.vue +98 -0
  304. package/fastify/src/resources/src/components/datatable/Pagination.vue +147 -0
  305. package/fastify/src/resources/src/components/datatable/SelectOpption.vue +64 -0
  306. package/fastify/src/resources/src/components/datatable/index.vue +199 -0
  307. package/fastify/src/resources/src/composables/useAuth.ts +52 -0
  308. package/fastify/src/resources/src/helpers/assets.ts +51 -0
  309. package/fastify/src/resources/src/helpers/nformatter.ts +10 -0
  310. package/fastify/src/resources/src/helpers/utils.ts +75 -0
  311. package/fastify/src/resources/src/layouts/AuthLayout.vue +20 -0
  312. package/fastify/src/resources/src/layouts/Layout/Footer.vue +23 -0
  313. package/fastify/src/resources/src/layouts/Layout/Header.vue +124 -0
  314. package/fastify/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
  315. package/fastify/src/resources/src/layouts/Layout/index.vue +87 -0
  316. package/fastify/src/resources/src/main.ts +28 -0
  317. package/fastify/src/resources/src/pages/auth/forgetPassword.vue +76 -0
  318. package/fastify/src/resources/src/pages/auth/login.vue +96 -0
  319. package/fastify/src/resources/src/pages/auth/register.vue +129 -0
  320. package/fastify/src/resources/src/pages/auth/resetPassword.vue +110 -0
  321. package/fastify/src/resources/src/pages/auth/verifyEmail.vue +64 -0
  322. package/fastify/src/resources/src/pages/dashboard/index.vue +66 -0
  323. package/fastify/src/resources/src/plugins/axios.ts +31 -0
  324. package/fastify/src/resources/src/plugins/browserDetect.ts +55 -0
  325. package/fastify/src/resources/src/plugins/dialog.ts +216 -0
  326. package/fastify/src/resources/src/plugins/gum.ts +683 -0
  327. package/fastify/src/resources/src/plugins/pulse.ts +157 -0
  328. package/fastify/src/resources/src/plugins/routeProgress.ts +87 -0
  329. package/fastify/src/resources/src/router/index.ts +85 -0
  330. package/fastify/src/resources/src/stores/admin-ui.ts +148 -0
  331. package/fastify/src/resources/src/stores/auth.ts +187 -0
  332. package/fastify/src/resources/tsconfig.json +19 -0
  333. package/fastify/src/resources/vite.config.ts +64 -0
  334. package/fastify/src/storage/app/private/.gitkeep +0 -0
  335. package/fastify/src/storage/app/public/.gitkeep +0 -0
  336. package/fastify/src/storage/app/tmp/generated/.gitkeep +0 -0
  337. package/fastify/src/storage/database/nexwire.sqlite +0 -0
  338. package/fastify/src/storage/logs/.gitkeep +0 -0
  339. package/fastify/src/storage/logs/app.log +0 -0
  340. package/fastify/src/storage/logs/fatal.log +0 -0
  341. package/fastify/src/storage/tmp/.gitkeep +0 -0
  342. package/fastify/src/storage/trash/modules/.gitkeep +0 -0
  343. package/fastify/src/types/global.d.ts +32 -0
  344. package/fastify/tsconfig.json +19 -0
  345. package/fastify/vitest.config.ts +38 -0
  346. package/hono/.env +1 -1
  347. package/hono/package.json +1 -1
  348. package/hono/src/config/database.ts +48 -29
  349. package/hono/src/config/index.ts +83 -83
  350. package/hono/src/config/openapi.ts +6 -24
  351. package/hono/src/config/queue.ts +30 -30
  352. package/hono/src/config/validate.ts +50 -40
  353. package/hono/src/database/migrations/sqlite/0000_init.sql +47 -47
  354. package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
  355. package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
  356. package/hono/src/framework/cache/cache.ts +40 -8
  357. package/hono/src/framework/circuit-breaker/cb.ts +88 -60
  358. package/hono/src/framework/database/connection.ts +52 -27
  359. package/hono/src/framework/events/dispatcher.ts +61 -49
  360. package/hono/src/framework/facade.ts +39 -1
  361. package/hono/src/framework/http/logger.ts +86 -81
  362. package/hono/src/framework/http/openapi.ts +7 -9
  363. package/hono/src/framework/maker-cli/runtime/core.mjs +1 -2
  364. package/hono/src/framework/notification/channels/broadcast.ts +15 -0
  365. package/hono/src/framework/notification/channels/mail.ts +42 -0
  366. package/hono/src/framework/notification/contracts.ts +29 -0
  367. package/hono/src/framework/notification/index.ts +19 -94
  368. package/hono/src/framework/notification/registry.ts +15 -0
  369. package/hono/src/framework/providers/adapter.ts +27 -17
  370. package/hono/src/framework/queue/queue.ts +43 -44
  371. package/hono/src/framework/realtime/broadcast.ts +29 -5
  372. package/hono/src/framework/realtime/socket.ts +217 -196
  373. package/hono/src/framework/redis/client.ts +148 -151
  374. package/hono/src/framework/scheduler/lock.ts +128 -116
  375. package/hono/src/framework/server.ts +27 -29
  376. package/hono/src/framework/session/session.ts +62 -14
  377. package/hono/src/framework/storage/storage.ts +744 -785
  378. package/hono/src/framework/support/mail.ts +56 -48
  379. package/hono/src/middlewares/auth-middleware.ts +1 -2
  380. package/hono/src/middlewares/role-middleware.ts +3 -13
  381. package/hono/src/modules/auth/routes/index.ts +1 -2
  382. package/hono/src/providers/notification.ts +4 -0
  383. package/hono/src/resources/src/components/Switch.vue +60 -65
  384. package/hono/src/resources/src/layouts/Layout/Header.vue +124 -105
  385. package/hono/src/resources/src/pages/auth/forgetPassword.vue +23 -29
  386. package/hono/src/resources/src/pages/auth/login.vue +26 -29
  387. package/hono/src/resources/src/pages/auth/register.vue +36 -40
  388. package/hono/src/resources/src/pages/auth/resetPassword.vue +29 -37
  389. package/hono/src/resources/src/pages/auth/verifyEmail.vue +15 -19
  390. package/hono/src/resources/src/plugins/gum.ts +519 -203
  391. package/hono/src/resources/src/plugins/pulse.ts +157 -147
  392. package/hono/src/resources/src/stores/auth.ts +9 -6
  393. package/hono/src/resources/vite.config.ts +54 -43
  394. package/hono/src/storage/database/nexwire.sqlite +0 -0
  395. package/hono/src/storage/logs/app.log +0 -0
  396. package/hono/src/storage/logs/fatal.log +0 -0
  397. package/hono/src/types/global.d.ts +32 -26
  398. package/package.json +4 -3
  399. package/src/index.js +143 -15
@@ -0,0 +1,134 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { cookie, HttpStatusCodes, jwt, resolveAuthProvider } from "@/framework/facade.js";
3
+
4
+ function unauthorized(reply: FastifyReply, message = "Unauthorized") {
5
+ return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message });
6
+ }
7
+
8
+ /**
9
+ * Why: Resolves the access token into a request auth context.
10
+ * When: Protected route groups run this as a scoped `preHandler` hook.
11
+ * Where: `modules/<name>/routes/*.ts` via `addHook("preHandler", authMiddleware)`.
12
+ * How: Verifies the access token, falls back to a rotating refresh token when
13
+ * needed, and exposes the resolved context as `request.auth`.
14
+ */
15
+ export async function authMiddleware(request: FastifyRequest, reply: FastifyReply) {
16
+ const provider = await resolveAuthProvider();
17
+ if (!provider) {
18
+ console.warn(
19
+ "[auth-middleware] auth provider not available; skipping auth. Register one in src/providers/ or restore src/modules/auth."
20
+ );
21
+ return;
22
+ }
23
+
24
+ const accessToken = await cookie.getAuth(request);
25
+
26
+ if (accessToken) {
27
+ const accessPayload: any = await jwt.verifyToken(accessToken, "access");
28
+
29
+ if (accessPayload) {
30
+ const user = await provider.getUserById((accessPayload as any)?.id);
31
+
32
+ if (!user) {
33
+ cookie.deleteAuth(reply);
34
+ cookie.deleteRefresh(reply);
35
+ return unauthorized(reply);
36
+ }
37
+
38
+ let authCtx: any;
39
+ if (provider.getAuthContext) {
40
+ authCtx = await provider.getAuthContext(user, accessPayload);
41
+ } else {
42
+ authCtx = {
43
+ ...(accessPayload || {}),
44
+ id: (user as any).id,
45
+ email: (user as any).email,
46
+ roleId: (user as any).role?.id ?? (user as any).roleId ?? null,
47
+ role: (user as any).role?.name ?? (user as any).role ?? null,
48
+ ...(user as any)
49
+ };
50
+ }
51
+ request.auth = authCtx;
52
+ return;
53
+ }
54
+ }
55
+
56
+ const refreshToken = await cookie.getRefresh(request);
57
+
58
+ if (!refreshToken) {
59
+ cookie.deleteAuth(reply);
60
+ cookie.deleteRefresh(reply);
61
+ return unauthorized(reply);
62
+ }
63
+
64
+ const refreshPayload: any = await jwt.verifyToken(refreshToken, "refresh");
65
+
66
+ if (!refreshPayload?.jti) {
67
+ cookie.deleteAuth(reply);
68
+ cookie.deleteRefresh(reply);
69
+ return unauthorized(reply, "Invalid token");
70
+ }
71
+
72
+ const storedToken = await provider.findRefreshToken((refreshPayload as any)?.jti);
73
+
74
+ if (!storedToken || (storedToken as any).revoked) {
75
+ cookie.deleteAuth(reply);
76
+ cookie.deleteRefresh(reply);
77
+ return unauthorized(reply, "Invalid token");
78
+ }
79
+
80
+ const expiresAt = (storedToken as any).expiresAt;
81
+ if (expiresAt) {
82
+ const exp = expiresAt instanceof Date ? expiresAt.getTime() : new Date(expiresAt).getTime();
83
+ if (!Number.isNaN(exp) && exp < Date.now()) {
84
+ if (provider.deleteRefreshToken) await provider.deleteRefreshToken((storedToken as any).id);
85
+ else if (provider.revokeRefreshToken) await provider.revokeRefreshToken((storedToken as any).id);
86
+ cookie.deleteAuth(reply);
87
+ cookie.deleteRefresh(reply);
88
+ return unauthorized(reply, "Invalid token");
89
+ }
90
+ }
91
+
92
+ const user = await provider.getUserById((refreshPayload as any)?.id);
93
+
94
+ if (!user) {
95
+ cookie.deleteAuth(reply);
96
+ cookie.deleteRefresh(reply);
97
+ return unauthorized(reply);
98
+ }
99
+
100
+ let baseCtx: any;
101
+ if (provider.getAuthContext) {
102
+ baseCtx = await provider.getAuthContext(user, refreshPayload);
103
+ } else {
104
+ baseCtx = {
105
+ ...(refreshPayload || {}),
106
+ id: (user as any).id,
107
+ email: (user as any).email,
108
+ roleId: (user as any).role?.id ?? (user as any).roleId ?? null,
109
+ role: (user as any).role?.name ?? (user as any).role ?? null,
110
+ ...(user as any)
111
+ };
112
+ }
113
+
114
+ const newAccessToken = await jwt.generateToken(
115
+ {
116
+ id: (user as any).id,
117
+ email: (user as any).email,
118
+ roleId: baseCtx?.roleId ?? (user as any).role?.id ?? (user as any).roleId ?? null,
119
+ role: baseCtx?.role ?? (user as any).role?.name ?? (user as any).role ?? null
120
+ },
121
+ "access"
122
+ );
123
+
124
+ await cookie.setAuth(reply, newAccessToken.token);
125
+ request.auth = {
126
+ ...(baseCtx || {}),
127
+ id: (user as any).id,
128
+ email: (user as any).email,
129
+ roleId: baseCtx?.roleId ?? (user as any).role?.id ?? (user as any).roleId ?? null,
130
+ role: baseCtx?.role ?? (user as any).role?.name ?? (user as any).role ?? null,
131
+ type: "access",
132
+ ...(user as any)
133
+ };
134
+ }
@@ -0,0 +1,19 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { HttpStatusCodes } from "@/framework/facade.js";
3
+
4
+ /**
5
+ * Why: Restricts a route (or route group) to users holding one of the roles.
6
+ * When: Declared after `authMiddleware` in a route's `preHandler` list.
7
+ * Where: `modules/<name>/routes/*.ts`.
8
+ * How: Reads `request.auth`, which `authMiddleware` populates.
9
+ */
10
+ export function requireRole(...allowedRoles: string[]) {
11
+ return async (request: FastifyRequest, reply: FastifyReply) => {
12
+ const auth = request.auth;
13
+ if (!auth) return reply.code(HttpStatusCodes.UNAUTHORIZED).send({ message: "Unauthorized" });
14
+
15
+ if (!allowedRoles.includes(auth.role?.name)) {
16
+ return reply.code(HttpStatusCodes.FORBIDDEN).send({ message: "Forbidden: Insufficient permissions" });
17
+ }
18
+ };
19
+ }
@@ -0,0 +1,200 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("@/framework/facade.js", () => ({
4
+ db: {
5
+ transaction: vi.fn()
6
+ },
7
+ dispatchEvent: vi.fn(),
8
+ urls: { url: vi.fn((p: string) => `https://app.test${p}`) }
9
+ }));
10
+
11
+ vi.mock("@/modules/auth/helpers/auth.js", () => ({
12
+ makeResetToken: vi.fn(() => "plain-token-123"),
13
+ hashResetToken: vi.fn((t: string) => `hashed:${t}`)
14
+ }));
15
+
16
+ vi.mock("@/modules/auth/database/models/user.js", () => ({
17
+ users: { __table: "users", id: "users.id", email: "users.email" },
18
+ passwordResetTokens: {
19
+ __table: "passwordResetTokens",
20
+ email: "passwordResetTokens.email",
21
+ token: "passwordResetTokens.token",
22
+ expiresAt: "passwordResetTokens.expiresAt",
23
+ createdAt: "passwordResetTokens.createdAt"
24
+ }
25
+ }));
26
+
27
+ import { db, dispatchEvent, urls } from "@/framework/facade.js";
28
+ import { hashResetToken, makeResetToken } from "@/modules/auth/helpers/auth.js";
29
+ import { forgotPasswordService } from "@/modules/auth/services/forgetpassword.js";
30
+
31
+ const fakeUser = {
32
+ id: 1,
33
+ email: "user@example.com",
34
+ name: "Jane"
35
+ };
36
+
37
+ const body = { email: "user@example.com" };
38
+
39
+ /**
40
+ * Build a fake tx whose calls we can inspect.
41
+ * - user: what query.users.findFirst returns (null = no user)
42
+ */
43
+ function buildTx({ user = fakeUser as unknown | null } = {}) {
44
+ const deleteCalls: { table: unknown; where: unknown }[] = [];
45
+ let currentDeleteTable: unknown;
46
+
47
+ const txDeleteWhere = vi.fn((where: unknown) => {
48
+ deleteCalls.push({ table: currentDeleteTable, where });
49
+ return Promise.resolve(undefined);
50
+ });
51
+ const txDelete = vi.fn((table: unknown) => {
52
+ currentDeleteTable = table;
53
+ return { where: txDeleteWhere };
54
+ });
55
+
56
+ const insertCalls: { table: unknown; values: unknown }[] = [];
57
+ let currentInsertTable: unknown;
58
+
59
+ const txInsertValues = vi.fn((values: unknown) => {
60
+ insertCalls.push({ table: currentInsertTable, values });
61
+ return Promise.resolve(undefined);
62
+ });
63
+ const txInsert = vi.fn((table: unknown) => {
64
+ currentInsertTable = table;
65
+ return { values: txInsertValues };
66
+ });
67
+
68
+ const txFindFirst = vi.fn((_args: unknown) => Promise.resolve(user));
69
+
70
+ return {
71
+ tx: {
72
+ delete: txDelete,
73
+ insert: txInsert,
74
+ query: { users: { findFirst: txFindFirst } }
75
+ },
76
+ txDelete,
77
+ txDeleteWhere,
78
+ txInsert,
79
+ txInsertValues,
80
+ txFindFirst,
81
+ deleteCalls,
82
+ insertCalls
83
+ };
84
+ }
85
+
86
+ describe("forgotPasswordService.forgotPassword", () => {
87
+ beforeEach(() => vi.clearAllMocks());
88
+
89
+ it("returns 'sent' and does nothing else when the user does not exist", async () => {
90
+ const { tx, txDelete, txInsert, txFindFirst } = buildTx({ user: null });
91
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
92
+
93
+ const result = await forgotPasswordService.forgotPassword(body);
94
+
95
+ expect(result).toEqual({
96
+ kind: "sent",
97
+ message: "Reset link has been sent"
98
+ });
99
+
100
+ // Only the housekeeping delete ran — no insert, no email
101
+ expect(txDelete).toHaveBeenCalledTimes(1);
102
+ expect(txFindFirst).toHaveBeenCalledTimes(1);
103
+ expect(txInsert).not.toHaveBeenCalled();
104
+ expect(dispatchEvent).not.toHaveBeenCalled();
105
+ expect(urls.url).not.toHaveBeenCalled();
106
+ });
107
+
108
+ it("issues a fresh token, stores its HASH, and dispatches the mail event", async () => {
109
+ const { tx, txInsertValues } = buildTx();
110
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
111
+
112
+ const result = await forgotPasswordService.forgotPassword(body);
113
+
114
+ expect(result).toEqual({
115
+ kind: "sent",
116
+ message: "Reset link has been sent"
117
+ });
118
+
119
+ // Plain token generated and hashed
120
+ expect(makeResetToken).toHaveBeenCalledTimes(1);
121
+ expect(hashResetToken).toHaveBeenCalledWith("plain-token-123");
122
+
123
+ // Inserted token is the HASH, never the plain one
124
+ expect(txInsertValues).toHaveBeenCalledTimes(1);
125
+ const inserted = txInsertValues.mock.calls[0][0] as any;
126
+ expect(inserted).toMatchObject({
127
+ email: "user@example.com",
128
+ token: "hashed:plain-token-123"
129
+ });
130
+ expect(inserted.token).not.toBe("plain-token-123");
131
+ expect(inserted.expiresAt).toBeInstanceOf(Date);
132
+ expect(inserted.createdAt).toBeInstanceOf(Date);
133
+
134
+ // ~15 min TTL between createdAt and expiresAt
135
+ const ttl = inserted.expiresAt.getTime() - inserted.createdAt.getTime();
136
+ expect(ttl).toBe(15 * 60 * 1000);
137
+
138
+ // Reset URL contains the PLAIN token (the user has to receive it)
139
+ expect(urls.url).toHaveBeenCalledWith("/reset-password?token=plain-token-123&email=user%40example.com");
140
+
141
+ // Mail dispatched with the reset URL
142
+ expect(dispatchEvent).toHaveBeenCalledWith(
143
+ "user:forget-password",
144
+ {
145
+ email: "user@example.com",
146
+ name: "Jane",
147
+ resetUrl: "https://app.test/reset-password?token=plain-token-123&email=user%40example.com"
148
+ },
149
+ { queue: "mail" }
150
+ );
151
+ });
152
+
153
+ it("revokes any existing tokens for the email before inserting a new one", async () => {
154
+ const { tx, txDelete, deleteCalls } = buildTx();
155
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
156
+
157
+ await forgotPasswordService.forgotPassword(body);
158
+
159
+ // Two deletes: expired cleanup + revoke existing
160
+ expect(txDelete).toHaveBeenCalledTimes(2);
161
+ expect(deleteCalls).toHaveLength(2);
162
+
163
+ // Both target passwordResetTokens
164
+ expect((deleteCalls[0].table as any).__table).toBe("passwordResetTokens");
165
+ expect((deleteCalls[1].table as any).__table).toBe("passwordResetTokens");
166
+ });
167
+
168
+ it("sends the same message whether or not the user exists (no enumeration)", async () => {
169
+ const missing = buildTx({ user: null });
170
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(missing.tx));
171
+ const r1 = await forgotPasswordService.forgotPassword(body);
172
+
173
+ vi.clearAllMocks();
174
+
175
+ const exists = buildTx();
176
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(exists.tx));
177
+ const r2 = await forgotPasswordService.forgotPassword(body);
178
+
179
+ expect(r1).toEqual(r2);
180
+ });
181
+
182
+ it("does not dispatch the email if the transaction fails", async () => {
183
+ vi.mocked(db.transaction).mockRejectedValueOnce(new Error("db down"));
184
+
185
+ await expect(forgotPasswordService.forgotPassword(body)).rejects.toThrow("db down");
186
+
187
+ expect(dispatchEvent).not.toHaveBeenCalled();
188
+ expect(urls.url).not.toHaveBeenCalled();
189
+ });
190
+
191
+ it("propagates a dispatch failure (token is already stored)", async () => {
192
+ const { tx } = buildTx();
193
+ vi.mocked(db.transaction).mockImplementationOnce(async (fn: any) => fn(tx));
194
+ vi.mocked(dispatchEvent).mockRejectedValueOnce(new Error("queue down"));
195
+
196
+ // Current behavior: dispatch failure aborts the request.
197
+ // The token IS in the DB — the user can retry "forgot password".
198
+ await expect(forgotPasswordService.forgotPassword(body)).rejects.toThrow("queue down");
199
+ });
200
+ });
@@ -0,0 +1,171 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ vi.mock("@/config/index.js", () => ({
5
+ authConfig: { requireEmailVerification: false }
6
+ }));
7
+
8
+ vi.mock("@/framework/facade.js", () => ({
9
+ db: { query: { users: { findFirst: vi.fn() } } },
10
+ password: { verifyPassword: vi.fn() }
11
+ }));
12
+
13
+ vi.mock("@/modules/auth/helpers/auth.js", () => ({
14
+ issueTokens: vi.fn(),
15
+ revokeCurrentRefreshToken: vi.fn(),
16
+ sanitizeUser: vi.fn((u: any) => ({ id: u.id, email: u.email, name: u.name }))
17
+ }));
18
+
19
+ import { authConfig } from "@/config/index.js";
20
+ import { db, password } from "@/framework/facade.js";
21
+ import { issueTokens, revokeCurrentRefreshToken } from "@/modules/auth/helpers/auth.js";
22
+ import { loginService } from "@/modules/auth/services/login.js";
23
+
24
+ const fakeUser = {
25
+ id: 1,
26
+ email: "user@example.com",
27
+ name: "Jane",
28
+ password: "hashed-pw",
29
+ emailVerifiedAt: new Date()
30
+ };
31
+
32
+ const body = { email: "user@example.com", password: "pw" };
33
+
34
+ /** Minimal fake req/res. The service just passes them through. */
35
+ function makeReqRes() {
36
+ const req = {} as FastifyRequest;
37
+ const res = {} as FastifyReply;
38
+ return { req, res };
39
+ }
40
+
41
+ describe("loginService.login (fastify)", () => {
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ vi.mocked(authConfig).requireEmailVerification = false;
45
+ });
46
+
47
+ it("returns invalid_credentials when the user does not exist (no hash attempt)", async () => {
48
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(undefined);
49
+ const { req, res } = makeReqRes();
50
+
51
+ const result = await loginService.login(req, res, body);
52
+
53
+ expect(result).toEqual({
54
+ kind: "invalid_credentials",
55
+ message: "Invalid credentials"
56
+ });
57
+ expect(password.verifyPassword).not.toHaveBeenCalled();
58
+ expect(issueTokens).not.toHaveBeenCalled();
59
+ expect(revokeCurrentRefreshToken).not.toHaveBeenCalled();
60
+ });
61
+
62
+ it("returns invalid_credentials when the password is wrong", async () => {
63
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(fakeUser as any);
64
+ vi.mocked(password.verifyPassword).mockResolvedValueOnce(false);
65
+ const { req, res } = makeReqRes();
66
+
67
+ const result = await loginService.login(req, res, body);
68
+
69
+ expect(result).toEqual({
70
+ kind: "invalid_credentials",
71
+ message: "Invalid credentials"
72
+ });
73
+ expect(password.verifyPassword).toHaveBeenCalledWith("pw", "hashed-pw");
74
+ expect(issueTokens).not.toHaveBeenCalled();
75
+ });
76
+
77
+ it("returns email_not_verified when verification is required and the email is not verified", async () => {
78
+ vi.mocked(authConfig).requireEmailVerification = true;
79
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce({
80
+ ...fakeUser,
81
+ emailVerifiedAt: null
82
+ } as any);
83
+ vi.mocked(password.verifyPassword).mockResolvedValueOnce(true);
84
+ const { req, res } = makeReqRes();
85
+
86
+ const result = await loginService.login(req, res, body);
87
+
88
+ expect(result).toEqual({
89
+ kind: "email_not_verified",
90
+ message: "Please verify your email before logging in"
91
+ });
92
+ expect(issueTokens).not.toHaveBeenCalled();
93
+ expect(revokeCurrentRefreshToken).not.toHaveBeenCalled();
94
+ });
95
+
96
+ it("does NOT check email verification when the feature is disabled", async () => {
97
+ vi.mocked(authConfig).requireEmailVerification = false;
98
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce({
99
+ ...fakeUser,
100
+ emailVerifiedAt: null
101
+ } as any);
102
+ vi.mocked(password.verifyPassword).mockResolvedValueOnce(true);
103
+ vi.mocked(issueTokens).mockResolvedValueOnce({
104
+ accessToken: "a",
105
+ refreshToken: "r"
106
+ } as any);
107
+ const { req, res } = makeReqRes();
108
+
109
+ const result = await loginService.login(req, res, body);
110
+
111
+ expect(result.kind).toBe("logged_in");
112
+ });
113
+
114
+ it("logs in successfully and passes req/res through to the helpers", async () => {
115
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(fakeUser as any);
116
+ vi.mocked(password.verifyPassword).mockResolvedValueOnce(true);
117
+ vi.mocked(issueTokens).mockResolvedValueOnce({
118
+ accessToken: "access-123",
119
+ refreshToken: "refresh-456"
120
+ } as any);
121
+ const { req, res } = makeReqRes();
122
+
123
+ const result = await loginService.login(req, res, {
124
+ ...body,
125
+ remember: true
126
+ });
127
+
128
+ // The Fastify contract: request and reply are forwarded as-is.
129
+ expect(revokeCurrentRefreshToken).toHaveBeenCalledWith(req);
130
+ expect(issueTokens).toHaveBeenCalledWith(req, res, fakeUser, {
131
+ remember: true
132
+ });
133
+
134
+ expect(result).toEqual({
135
+ kind: "logged_in",
136
+ message: "User logged in successfully",
137
+ data: {
138
+ user: { id: 1, email: "user@example.com", name: "Jane" },
139
+ access_token: "access-123",
140
+ refresh_token: "refresh-456",
141
+ token_type: "Bearer"
142
+ }
143
+ });
144
+ });
145
+
146
+ it("coerces a missing remember flag to false", async () => {
147
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(fakeUser as any);
148
+ vi.mocked(password.verifyPassword).mockResolvedValueOnce(true);
149
+ vi.mocked(issueTokens).mockResolvedValueOnce({
150
+ accessToken: "a",
151
+ refreshToken: "r"
152
+ } as any);
153
+ const { req, res } = makeReqRes();
154
+
155
+ await loginService.login(req, res, body);
156
+
157
+ expect(issueTokens).toHaveBeenCalledWith(req, res, fakeUser, {
158
+ remember: false
159
+ });
160
+ });
161
+
162
+ it("does not issue tokens when credentials are invalid", async () => {
163
+ vi.mocked(db.query.users.findFirst).mockResolvedValueOnce(undefined);
164
+ const { req, res } = makeReqRes();
165
+
166
+ await loginService.login(req, res, body);
167
+
168
+ expect(revokeCurrentRefreshToken).not.toHaveBeenCalled();
169
+ expect(issueTokens).not.toHaveBeenCalled();
170
+ });
171
+ });
@@ -0,0 +1,60 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+
4
+ vi.mock("@/framework/facade.js", () => ({
5
+ cookie: {
6
+ deleteAuth: vi.fn(),
7
+ deleteRefresh: vi.fn()
8
+ },
9
+ logger: {
10
+ error: vi.fn()
11
+ }
12
+ }));
13
+
14
+ vi.mock("@/modules/auth/helpers/auth.js", () => ({
15
+ revokeCurrentRefreshToken: vi.fn()
16
+ }));
17
+
18
+ import { cookie, logger } from "@/framework/facade.js";
19
+ import { revokeCurrentRefreshToken } from "@/modules/auth/helpers/auth.js";
20
+ import { logoutService } from "@/modules/auth/services/logout.js";
21
+
22
+ function makeReqRes() {
23
+ const req = {} as FastifyRequest;
24
+ const res = {} as FastifyReply;
25
+ return { req, res };
26
+ }
27
+
28
+ describe("logoutService.logout (fastify)", () => {
29
+ beforeEach(() => vi.clearAllMocks());
30
+
31
+ it("revokes the refresh token and clears cookies", async () => {
32
+ const { req, res } = makeReqRes();
33
+
34
+ const result = await logoutService.logout(req, res);
35
+
36
+ expect(revokeCurrentRefreshToken).toHaveBeenCalledWith(req);
37
+ expect(cookie.deleteAuth).toHaveBeenCalledWith(res);
38
+ expect(cookie.deleteRefresh).toHaveBeenCalledWith(res);
39
+ expect(result).toEqual({
40
+ kind: "logged_out",
41
+ message: "Logged out successfully"
42
+ });
43
+ });
44
+
45
+ it("still clears cookies and returns success if revoke throws", async () => {
46
+ const err = new Error("db down");
47
+ vi.mocked(revokeCurrentRefreshToken).mockRejectedValueOnce(err);
48
+
49
+ const { req, res } = makeReqRes();
50
+ const result = await logoutService.logout(req, res);
51
+
52
+ expect(cookie.deleteAuth).toHaveBeenCalledWith(res);
53
+ expect(cookie.deleteRefresh).toHaveBeenCalledWith(res);
54
+ expect(logger.error).toHaveBeenCalledWith("failed to revoke refresh token on logout", { err });
55
+ expect(result).toEqual({
56
+ kind: "logged_out",
57
+ message: "Logged out successfully"
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,49 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ vi.mock("drizzle-orm", () => ({
4
+ eq: vi.fn((col: unknown, val: unknown) => ({ op: "eq", col, val }))
5
+ }));
6
+
7
+ vi.mock("@/framework/facade.js", () => ({
8
+ db: {
9
+ delete: vi.fn(() => ({ where: vi.fn() }))
10
+ }
11
+ }));
12
+
13
+ vi.mock("@/modules/auth/database/models/user.js", () => ({
14
+ refreshTokens: { __table: "refreshTokens", userId: "refreshTokens.userId" }
15
+ }));
16
+
17
+ import { eq } from "drizzle-orm";
18
+ import { db } from "@/framework/facade.js";
19
+ import { refreshTokens } from "@/modules/auth/database/models/user.js";
20
+ import { logoutAllDevicesService } from "@/modules/auth/services/logoutalldevices.js";
21
+
22
+ describe("logoutAllDevicesService.logoutAllDevices", () => {
23
+ beforeEach(() => vi.clearAllMocks());
24
+
25
+ it("deletes refresh tokens for the given user", async () => {
26
+ await logoutAllDevicesService.logoutAllDevices(42);
27
+
28
+ expect(db.delete).toHaveBeenCalledWith(refreshTokens);
29
+
30
+ // The where-clause targets refreshTokens.userId with the userId value
31
+ expect(eq).toHaveBeenCalledWith(refreshTokens.userId, 42);
32
+
33
+ const whereArg = (db.delete as any).mock.results[0].value.where.mock.calls[0][0] as any;
34
+ expect(whereArg).toEqual({
35
+ op: "eq",
36
+ col: "refreshTokens.userId",
37
+ val: 42
38
+ });
39
+ });
40
+
41
+ it("returns logged_out", async () => {
42
+ const result = await logoutAllDevicesService.logoutAllDevices(42);
43
+
44
+ expect(result).toEqual({
45
+ kind: "logged_out",
46
+ message: "Logged out from all devices successfully"
47
+ });
48
+ });
49
+ });