create-bubbles 0.1.23 → 0.1.24

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 (230) hide show
  1. package/index.js +1 -1
  2. package/package.json +25 -24
  3. package/template-taro-react-oxc/.env.development +1 -1
  4. package/template-taro-react-oxc/.env.production +1 -1
  5. package/template-taro-react-oxc/project.config.json +1 -1
  6. package/template-taro-vue-eslint/project.config.json +2 -2
  7. package/{template-vp-monorepo-react-nestjs → template-vp-monorepo-react-hono}/.spaces/server//347/231/273/345/275/225/JWT/344/270/216RefreshToken/350/275/256/346/215/242/351/211/264/346/235/203/346/236/266/346/236/204/345/256/236/347/216/260/346/214/207/345/215/227.md +588 -1133
  8. package/template-vp-monorepo-react-hono/.vite-hooks/commit-msg +2 -0
  9. package/template-vp-monorepo-react-hono/.vite-hooks/pre-commit +2 -0
  10. package/template-vp-monorepo-react-hono/.vscode/extensions.json +3 -0
  11. package/template-vp-monorepo-react-hono/.vscode/settings.json +23 -0
  12. package/template-vp-monorepo-react-hono/AGENTS.md +16 -0
  13. package/template-vp-monorepo-react-hono/README.md +54 -0
  14. package/template-vp-monorepo-react-hono/apps/server/.env.example +13 -0
  15. package/template-vp-monorepo-react-hono/apps/server/README.md +75 -0
  16. package/template-vp-monorepo-react-hono/apps/server/drizzle/0000_flaky_madame_masque.sql +18 -0
  17. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/0000_snapshot.json +153 -0
  18. package/template-vp-monorepo-react-hono/apps/server/drizzle/meta/_journal.json +13 -0
  19. package/template-vp-monorepo-react-hono/apps/server/drizzle.config.ts +19 -0
  20. package/template-vp-monorepo-react-hono/apps/server/package.json +46 -0
  21. package/template-vp-monorepo-react-hono/apps/server/src/app.test.ts +57 -0
  22. package/template-vp-monorepo-react-hono/apps/server/src/app.ts +68 -0
  23. package/template-vp-monorepo-react-hono/apps/server/src/common/errors/app-error.ts +23 -0
  24. package/template-vp-monorepo-react-hono/apps/server/src/common/http/response.ts +21 -0
  25. package/template-vp-monorepo-react-hono/apps/server/src/common/logger.ts +30 -0
  26. package/template-vp-monorepo-react-hono/apps/server/src/config/env.ts +54 -0
  27. package/template-vp-monorepo-react-hono/apps/server/src/index.bun.ts +60 -0
  28. package/template-vp-monorepo-react-hono/apps/server/src/index.ts +73 -0
  29. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/client.ts +46 -0
  30. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/index.ts +1 -0
  31. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/database/schema/job-runs.ts +42 -0
  32. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/queue/connection.ts +6 -0
  33. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/client.ts +39 -0
  34. package/template-vp-monorepo-react-hono/apps/server/src/infrastructure/redis/options.ts +28 -0
  35. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.routes.ts +17 -0
  36. package/template-vp-monorepo-react-hono/apps/server/src/modules/health/health.service.ts +59 -0
  37. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.queue.ts +46 -0
  38. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.repository.ts +101 -0
  39. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.routes.ts +46 -0
  40. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.schemas.ts +9 -0
  41. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.service.ts +57 -0
  42. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.types.ts +11 -0
  43. package/template-vp-monorepo-react-hono/apps/server/src/modules/jobs/jobs.worker.ts +108 -0
  44. package/template-vp-monorepo-react-hono/apps/server/src/runtime/shutdown.ts +38 -0
  45. package/template-vp-monorepo-react-hono/apps/server/src/worker.ts +48 -0
  46. package/template-vp-monorepo-react-hono/apps/server/tsconfig.json +15 -0
  47. package/template-vp-monorepo-react-hono/apps/server/vite.config.ts +15 -0
  48. package/template-vp-monorepo-react-hono/apps/web/.env +2 -0
  49. package/template-vp-monorepo-react-hono/apps/web/.env.dev +1 -0
  50. package/template-vp-monorepo-react-hono/apps/web/.env.prod +1 -0
  51. package/template-vp-monorepo-react-hono/apps/web/index.html +13 -0
  52. package/template-vp-monorepo-react-hono/apps/web/package.json +35 -0
  53. package/template-vp-monorepo-react-hono/apps/web/public/favicon.svg +1 -0
  54. package/template-vp-monorepo-react-hono/apps/web/public/icons.svg +24 -0
  55. package/template-vp-monorepo-react-hono/apps/web/src/App.tsx +25 -0
  56. package/template-vp-monorepo-react-hono/apps/web/src/assets/hero.png +0 -0
  57. package/template-vp-monorepo-react-hono/apps/web/src/assets/react.svg +1 -0
  58. package/template-vp-monorepo-react-hono/apps/web/src/assets/vite.svg +1 -0
  59. package/template-vp-monorepo-react-hono/apps/web/src/components/Loading/PageLoading.tsx +11 -0
  60. package/template-vp-monorepo-react-hono/apps/web/src/locales/en_US.json +4 -0
  61. package/template-vp-monorepo-react-hono/apps/web/src/locales/zh_CN.json +4 -0
  62. package/template-vp-monorepo-react-hono/apps/web/src/main.tsx +5 -0
  63. package/template-vp-monorepo-react-hono/apps/web/src/pages/home/index.tsx +47 -0
  64. package/template-vp-monorepo-react-hono/apps/web/src/router/index.tsx +25 -0
  65. package/template-vp-monorepo-react-hono/apps/web/src/router/modules/.gitkeep +0 -0
  66. package/template-vp-monorepo-react-hono/apps/web/src/styles/index.css +7 -0
  67. package/template-vp-monorepo-react-hono/apps/web/src/types/auto-imports.d.ts +58 -0
  68. package/template-vp-monorepo-react-hono/apps/web/src/utils/env/index.ts +6 -0
  69. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/index.ts +420 -0
  70. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/core/utils.ts +39 -0
  71. package/template-vp-monorepo-react-hono/apps/web/src/utils/request/index.ts +61 -0
  72. package/template-vp-monorepo-react-hono/apps/web/tsconfig.json +18 -0
  73. package/template-vp-monorepo-react-hono/apps/web/vite.config.ts +45 -0
  74. package/template-vp-monorepo-react-hono/commitlint.config.js +1 -0
  75. package/template-vp-monorepo-react-hono/docker-compose.yml +41 -0
  76. package/template-vp-monorepo-react-hono/mise.toml +4 -0
  77. package/template-vp-monorepo-react-hono/package.json +37 -0
  78. package/template-vp-monorepo-react-hono/packages/i18n/core/package.json +20 -0
  79. package/template-vp-monorepo-react-hono/packages/i18n/core/src/format.ts +9 -0
  80. package/template-vp-monorepo-react-hono/packages/i18n/core/src/i18n.ts +73 -0
  81. package/template-vp-monorepo-react-hono/packages/i18n/core/src/index.ts +24 -0
  82. package/template-vp-monorepo-react-hono/packages/i18n/core/src/storage.ts +47 -0
  83. package/template-vp-monorepo-react-hono/packages/i18n/core/src/store.ts +48 -0
  84. package/template-vp-monorepo-react-hono/packages/i18n/core/src/types.ts +36 -0
  85. package/template-vp-monorepo-react-hono/packages/i18n/core/tsconfig.json +4 -0
  86. package/template-vp-monorepo-react-hono/packages/i18n/core/vite.config.ts +13 -0
  87. package/template-vp-monorepo-react-hono/packages/i18n/react/package.json +28 -0
  88. package/template-vp-monorepo-react-hono/packages/i18n/react/src/index.tsx +26 -0
  89. package/template-vp-monorepo-react-hono/packages/i18n/react/src/use-store.ts +14 -0
  90. package/template-vp-monorepo-react-hono/packages/i18n/react/tsconfig.json +9 -0
  91. package/template-vp-monorepo-react-hono/packages/i18n/react/vite.config.ts +16 -0
  92. package/template-vp-monorepo-react-hono/packages/i18n/vue/package.json +27 -0
  93. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/index.ts +52 -0
  94. package/template-vp-monorepo-react-hono/packages/i18n/vue/src/use-store.ts +19 -0
  95. package/template-vp-monorepo-react-hono/packages/i18n/vue/tsconfig.json +8 -0
  96. package/template-vp-monorepo-react-hono/packages/i18n/vue/vite.config.ts +16 -0
  97. package/template-vp-monorepo-react-hono/pnpm-workspace.yaml +57 -0
  98. package/template-vp-monorepo-react-hono/scripts/clean.mjs +48 -0
  99. package/template-vp-monorepo-react-hono/tsconfig.base.json +10 -0
  100. package/template-vp-monorepo-react-hono/tsconfig.bundler.json +12 -0
  101. package/template-vp-monorepo-react-hono/tsconfig.json +11 -0
  102. package/template-vp-monorepo-react-hono/tsconfig.lib.json +10 -0
  103. package/template-vp-monorepo-react-hono/vite.config.ts +20 -0
  104. package/template-vp-monorepo-react-nestjs/.spaces/server/todo.md +991 -0
  105. package/template-vp-monorepo-react-nestjs/.vscode/launch.json +16 -0
  106. package/template-vp-monorepo-react-nestjs/.vscode/settings.json +21 -18
  107. package/template-vp-monorepo-react-nestjs/apps/server/.env.development +3 -39
  108. package/template-vp-monorepo-react-nestjs/apps/server/.swcrc +8 -0
  109. package/template-vp-monorepo-react-nestjs/apps/server/drizzle.config.ts +8 -3
  110. package/template-vp-monorepo-react-nestjs/apps/server/nest-cli.json +8 -1
  111. package/template-vp-monorepo-react-nestjs/apps/server/package.json +11 -6
  112. package/template-vp-monorepo-react-nestjs/apps/server/src/app.controller.ts +2 -0
  113. package/template-vp-monorepo-react-nestjs/apps/server/src/app.module.ts +20 -15
  114. package/template-vp-monorepo-react-nestjs/apps/server/src/common/adapters/fastify.adapter.ts +16 -0
  115. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/auth.ts +2 -0
  116. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/response.ts +0 -4
  117. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/session.constants.ts +28 -0
  118. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/current-auth.decorator.ts +16 -0
  119. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/public.decorator.ts +4 -0
  120. package/template-vp-monorepo-react-nestjs/apps/server/src/common/dto/api-failure.dto.ts +25 -0
  121. package/template-vp-monorepo-react-nestjs/apps/server/src/common/error/common.error.ts +40 -0
  122. package/template-vp-monorepo-react-nestjs/apps/server/src/common/exceptions/app.exception.ts +55 -0
  123. package/template-vp-monorepo-react-nestjs/apps/server/src/common/filters/global-exception.filter.ts +384 -0
  124. package/template-vp-monorepo-react-nestjs/apps/server/src/common/openapi/response-headers.ts +9 -0
  125. package/template-vp-monorepo-react-nestjs/apps/server/src/config/index.ts +1 -1
  126. package/template-vp-monorepo-react-nestjs/apps/server/src/config/session.config.ts +36 -0
  127. package/template-vp-monorepo-react-nestjs/apps/server/src/database/schema.ts +4 -64
  128. package/template-vp-monorepo-react-nestjs/apps/server/src/main.ts +29 -8
  129. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.controller.ts +55 -0
  130. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.errors.ts +42 -0
  131. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.module.ts +26 -0
  132. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.repository.ts +40 -0
  133. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/auth.service.ts +129 -0
  134. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/auth-repsponse.dto.ts +31 -0
  135. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/login.dto.ts +11 -0
  136. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/dto/register.dto.ts +17 -0
  137. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/guards/session-auth.guard.ts +50 -0
  138. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/password.service.ts +24 -0
  139. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-store.service.ts +198 -0
  140. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session-token.service.ts +29 -0
  141. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.script.ts +221 -0
  142. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/auth/session/session.types.ts +24 -0
  143. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/db.controller.ts +1 -1
  144. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/db/dto/create-user.dto.ts +3 -5
  145. package/template-vp-monorepo-react-nestjs/apps/server/src/modules/test/redis/redis.service.ts +1 -1
  146. package/template-vp-monorepo-react-nestjs/apps/server/src/utils/env-arr.ts +3 -0
  147. package/template-vp-monorepo-react-nestjs/apps/server/test/error-catalog.spec.ts +30 -0
  148. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.build.json +11 -1
  149. package/template-vp-monorepo-react-nestjs/apps/server/tsconfig.json +12 -20
  150. package/template-vp-monorepo-react-nestjs/apps/server/vite.config.ts +15 -0
  151. package/template-vp-monorepo-react-nestjs/apps/web/package.json +1 -0
  152. package/template-vp-monorepo-react-nestjs/apps/web/tsconfig.json +5 -15
  153. package/template-vp-monorepo-react-nestjs/apps/web-vue/commitlint.config.js +1 -1
  154. package/template-vp-monorepo-react-nestjs/apps/web-vue/lefthook.yml +1 -1
  155. package/template-vp-monorepo-react-nestjs/apps/web-vue/package.json +1 -3
  156. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/api/index.ts +5 -5
  157. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/components/Icon/svg-icon.vue +3 -3
  158. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/lib.ts +6 -6
  159. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/hooks/chart/useEcharts.ts +34 -29
  160. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/i18n/instance.ts +2 -0
  161. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/layout/default/index.vue +17 -32
  162. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/en_US.json +4 -1
  163. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/locales/zh_CN.json +4 -1
  164. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/index.ts +3 -3
  165. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/guard/permissionGuard.ts +7 -7
  166. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/index.ts +7 -7
  167. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/router/interface.ts +6 -6
  168. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/index.ts +6 -6
  169. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/store/modules/user.ts +9 -9
  170. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/styles/index.scss +3 -3
  171. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/types/antdv-next.d.ts +1 -0
  172. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/env.ts +1 -1
  173. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/index.ts +63 -45
  174. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/core/utils.ts +12 -6
  175. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/utils/request/index.ts +1 -1
  176. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/config.ts +687 -687
  177. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/echart/index.vue +7 -7
  178. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/example/tree-chart.vue +2 -7
  179. package/template-vp-monorepo-react-nestjs/apps/web-vue/src/views/home/index.vue +13 -3
  180. package/template-vp-monorepo-react-nestjs/apps/web-vue/tsconfig.json +5 -17
  181. package/template-vp-monorepo-react-nestjs/apps/web-vue/uno.config.ts +5 -5
  182. package/template-vp-monorepo-react-nestjs/i18n.config.ts +22 -0
  183. package/template-vp-monorepo-react-nestjs/package.json +12 -1
  184. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/README.md +113 -0
  185. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/docs/excel-sync-plan.md +618 -0
  186. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/package.json +31 -0
  187. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/cli.ts +5 -0
  188. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/command.ts +512 -0
  189. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/config.ts +15 -0
  190. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/files.ts +67 -0
  191. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/index.ts +19 -0
  192. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/load-config.ts +228 -0
  193. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/report.ts +57 -0
  194. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.test.ts +81 -0
  195. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/scanner.ts +290 -0
  196. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/src/sync.ts +274 -0
  197. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/command.test.ts +209 -0
  198. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/config.test.ts +19 -0
  199. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/load-config.test.ts +194 -0
  200. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/report.test.ts +63 -0
  201. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/sync.test.ts +203 -0
  202. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/test/temporary-directory.ts +25 -0
  203. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/tsconfig.json +9 -0
  204. package/template-vp-monorepo-react-nestjs/packages/i18n/cli/vite.config.ts +18 -0
  205. package/template-vp-monorepo-react-nestjs/packages/i18n/core/tsconfig.json +1 -12
  206. package/template-vp-monorepo-react-nestjs/packages/i18n/react/tsconfig.json +3 -10
  207. package/template-vp-monorepo-react-nestjs/packages/i18n/vue/tsconfig.json +3 -10
  208. package/template-vp-monorepo-react-nestjs/packages/shared/package.json +40 -0
  209. package/template-vp-monorepo-react-nestjs/packages/shared/src/index.ts +13 -0
  210. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/auth.ts +35 -0
  211. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/common.ts +11 -0
  212. package/template-vp-monorepo-react-nestjs/packages/shared/src/types/index.ts +11 -0
  213. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/account.ts +5 -0
  214. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/index.ts +2 -0
  215. package/template-vp-monorepo-react-nestjs/packages/shared/src/utils/session-terminal.ts +11 -0
  216. package/template-vp-monorepo-react-nestjs/packages/shared/tsconfig.json +4 -0
  217. package/template-vp-monorepo-react-nestjs/packages/shared/vite.config.ts +17 -0
  218. package/template-vp-monorepo-react-nestjs/pnpm-workspace.yaml +6 -6
  219. package/template-vp-monorepo-react-nestjs/scripts/clean.mjs +48 -0
  220. package/template-vp-monorepo-react-nestjs/tsconfig.base.json +10 -0
  221. package/template-vp-monorepo-react-nestjs/tsconfig.bundler.json +12 -0
  222. package/template-vp-monorepo-react-nestjs/tsconfig.json +8 -6
  223. package/template-vp-monorepo-react-nestjs/tsconfig.lib.json +10 -0
  224. package/template-vp-monorepo-react-nestjs/apps/server/src/common/constants/decorator.ts +0 -2
  225. package/template-vp-monorepo-react-nestjs/apps/server/src/common/decorators/bypass.ts +0 -12
  226. package/template-vp-monorepo-react-nestjs/apps/server/src/common/interceptors/transform.ts +0 -34
  227. package/template-vp-monorepo-react-nestjs/apps/server/src/common/model/response.ts +0 -23
  228. package/template-vp-monorepo-react-nestjs/apps/server/src/config/auth.config.ts +0 -64
  229. package/template-vp-monorepo-react-nestjs/apps/web-vue/pnpm-workspace.yaml +0 -16
  230. /package/{template-vp-monorepo-react-nestjs/apps/server/src/config/security.config.ts → template-vp-monorepo-react-hono/.agents/auth.config.ts} +0 -0
@@ -0,0 +1,26 @@
1
+ import { Module } from '@nestjs/common'
2
+ import { APP_GUARD } from '@nestjs/core'
3
+ import { AuthController } from './auth.controller'
4
+ import { AuthRepository } from './auth.repository'
5
+ import { AuthService } from './auth.service'
6
+ import { SessionAuthGuard } from './guards/session-auth.guard'
7
+ import { PasswordService } from './password.service'
8
+ import { SessionStoreService } from './session/session-store.service'
9
+ import { SessionTokenService } from './session/session-token.service'
10
+
11
+ @Module({
12
+ controllers: [AuthController],
13
+ providers: [
14
+ AuthRepository,
15
+ AuthService,
16
+ PasswordService,
17
+ SessionTokenService,
18
+ SessionStoreService,
19
+ {
20
+ provide: APP_GUARD,
21
+ useClass: SessionAuthGuard,
22
+ },
23
+ ],
24
+ exports: [SessionStoreService],
25
+ })
26
+ export class AuthModule {}
@@ -0,0 +1,40 @@
1
+ import { DRIZZLE, type DrizzleDB } from '@/database/db.module'
2
+ import { users } from '@/database/schema'
3
+ import { Inject, Injectable } from '@nestjs/common'
4
+ import { eq } from 'drizzle-orm'
5
+
6
+ type CreateUserInput = Pick<typeof users.$inferInsert, 'name' | 'account' | 'passwordHash'>
7
+
8
+ @Injectable()
9
+ export class AuthRepository {
10
+ constructor(@Inject(DRIZZLE) private readonly db: DrizzleDB) {}
11
+
12
+ async findByAccount(account: string) {
13
+ const [user] = await this.db.select().from(users).where(eq(users.account, account)).limit(1)
14
+ return user ?? null
15
+ }
16
+
17
+ async findPublicById(userId: string) {
18
+ const [user] = await this.db
19
+ .select({
20
+ id: users.id,
21
+ account: users.account,
22
+ name: users.name,
23
+ status: users.status,
24
+ })
25
+ .from(users)
26
+ .where(eq(users.id, userId))
27
+ .limit(1)
28
+ return user ?? null
29
+ }
30
+
31
+ async createUser(input: CreateUserInput) {
32
+ const [user] = await this.db
33
+ .insert(users)
34
+ .values(input)
35
+ // 处理账号重复情况 ,不插入重复账号
36
+ .onConflictDoNothing({ target: users.account })
37
+ .returning()
38
+ return user ?? null
39
+ }
40
+ }
@@ -0,0 +1,129 @@
1
+ import { detectSessionTerminal } from '@/common/constants/session.constants'
2
+ import { AppException } from '@/common/exceptions/app.exception'
3
+ import { Injectable } from '@nestjs/common'
4
+ import { ConfigService } from '@nestjs/config'
5
+ import { AuthUser, LogoutResult, RegisterResult } from 'shared/types'
6
+ import { normalizeAccount } from 'shared/utils'
7
+ import { AUTH_ERRORS } from './auth.errors'
8
+ import { AuthRepository } from './auth.repository'
9
+ import { LoginDto } from './dto/login.dto'
10
+ import { RegisterDto } from './dto/register.dto'
11
+ import { PasswordService } from './password.service'
12
+ import { SessionStoreService } from './session/session-store.service'
13
+ import { SessionTokenService } from './session/session-token.service'
14
+
15
+ interface LoginMetadata {
16
+ ip: string
17
+ userAgent: string
18
+ }
19
+
20
+ const LOGIN_DUMMY_PASSWORD_HASH =
21
+ '$argon2id$v=19$m=65536,p=4,t=3$/sDN9pcO6kkAcm+ncFtbdA$vmQIsQtUG9E+s22x70njKji67RJDseq12y/sYFG7iNk'
22
+
23
+ @Injectable()
24
+ export class AuthService {
25
+ private readonly idleExpiresIn: number
26
+
27
+ constructor(
28
+ private readonly authRepository: AuthRepository,
29
+ private readonly passwordService: PasswordService,
30
+ private readonly sessionTokenService: SessionTokenService,
31
+ private readonly sessionStoreService: SessionStoreService,
32
+ config: ConfigService,
33
+ ) {
34
+ this.idleExpiresIn = Math.floor(config.getOrThrow<number>('session.idleTtlMs') / 1000)
35
+ }
36
+
37
+ async register(input: RegisterDto): Promise<RegisterResult> {
38
+ const account = normalizeAccount(input.account)
39
+ const passwordHash = await this.useAuthInfrastrutrue(() =>
40
+ this.passwordService.hash(input.password),
41
+ )
42
+ const user = await this.useAuthInfrastrutrue(() =>
43
+ this.authRepository.createUser({
44
+ name: input.name.trim(),
45
+ account,
46
+ passwordHash,
47
+ }),
48
+ )
49
+ if (!user) {
50
+ throw new AppException(AUTH_ERRORS.ACCOUNT_ALREADY_EXISTS)
51
+ }
52
+
53
+ return {
54
+ id: user.id,
55
+ name: user.name,
56
+ account: user.account,
57
+ }
58
+ }
59
+
60
+ async login(input: LoginDto, metadata: LoginMetadata) {
61
+ const account = normalizeAccount(input.account)
62
+ const user = await this.useAuthInfrastrutrue(() => this.authRepository.findByAccount(account))
63
+ const passwordHash = user?.status === 'active' ? user.passwordHash : LOGIN_DUMMY_PASSWORD_HASH
64
+ const passwordMatches = await this.useAuthInfrastrutrue(() =>
65
+ this.passwordService.verify(passwordHash, input.password),
66
+ )
67
+
68
+ if (!user || user.status !== 'active' || !passwordMatches) {
69
+ throw new AppException(AUTH_ERRORS.INVALID_CREDENTIALS)
70
+ }
71
+
72
+ const { rawToken, tokenDigest } = this.sessionTokenService.createToken()
73
+
74
+ const session = await this.sessionStoreService.createOrReplace({
75
+ tokenDigest,
76
+ userId: user.id,
77
+ terminal: detectSessionTerminal(metadata.userAgent),
78
+ loginIp: metadata.ip.slice(0, 64),
79
+ userAgent: metadata.userAgent.slice(0, 500),
80
+ })
81
+
82
+ return {
83
+ accessToken: rawToken,
84
+ tokenType: 'Bearer' as const,
85
+ idleExpiresIn: this.idleExpiresIn,
86
+ absoluteExpiresAt: new Date(session.absoluteExpiresAtMs).toISOString(),
87
+ }
88
+ }
89
+
90
+ private async useAuthInfrastrutrue<T>(operation: () => Promise<T>): Promise<T> {
91
+ try {
92
+ return await operation()
93
+ } catch (cause: unknown) {
94
+ if (cause instanceof AppException) {
95
+ throw cause
96
+ }
97
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, { cause })
98
+ }
99
+ }
100
+
101
+ async getCurrentUser(userId: string): Promise<AuthUser> {
102
+ const user = await this.useAuthInfrastrutrue(() =>
103
+ this.authRepository.findPublicById(userId).catch((cause: unknown) => {
104
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, { cause })
105
+ }),
106
+ )
107
+
108
+ if (!user || user.status !== 'active') {
109
+ throw new AppException(AUTH_ERRORS.SESSION_INVALID)
110
+ }
111
+
112
+ return {
113
+ id: user.id,
114
+ account: user.account,
115
+ name: user.name,
116
+ }
117
+ }
118
+
119
+ async logout(authorization: string | undefined): Promise<LogoutResult> {
120
+ const rawToken = this.sessionTokenService.extractBearerToken(authorization)
121
+ if (!rawToken) {
122
+ return { loggedOut: true }
123
+ }
124
+
125
+ const tokenDigest = this.sessionTokenService.digest(rawToken)
126
+ await this.sessionStoreService.logout(tokenDigest)
127
+ return { loggedOut: true }
128
+ }
129
+ }
@@ -0,0 +1,31 @@
1
+ import { createZodDto } from 'nestjs-zod'
2
+ import z from 'zod'
3
+
4
+ const authUserSchema = z.object({
5
+ id: z.string().uuid(),
6
+ account: z.string(),
7
+ name: z.string(),
8
+ })
9
+
10
+ export class RegisterResultDto extends createZodDto(authUserSchema) {}
11
+
12
+ export class LoginResultDto extends createZodDto(
13
+ z.object({
14
+ accessToken: z.string().min(1),
15
+ tokenType: z.literal('Bearer'),
16
+ idleExpiresIn: z.number().int().positive(),
17
+ absoluteExpiresAt: z.string().datetime(),
18
+ }),
19
+ ) {}
20
+
21
+ export class CurrentUserDto extends createZodDto(
22
+ authUserSchema.extend({
23
+ terminal: z.enum(['web', 'desktop', 'mobile']),
24
+ }),
25
+ ) {}
26
+
27
+ export class LogoutResultDto extends createZodDto(
28
+ z.object({
29
+ loggedOut: z.literal(true),
30
+ }),
31
+ ) {}
@@ -0,0 +1,11 @@
1
+ import { createZodDto } from 'nestjs-zod'
2
+ import { LoginRequest } from 'shared/types'
3
+ import { ACCOUNT_PATTERN } from 'shared/utils'
4
+ import z from 'zod'
5
+
6
+ export const loginSchema = z.object({
7
+ account: z.string().trim().min(4).max(32).regex(ACCOUNT_PATTERN, '账号格式错误'),
8
+ password: z.string().min(1).max(128),
9
+ }) satisfies z.ZodType<LoginRequest>
10
+
11
+ export class LoginDto extends createZodDto(loginSchema) {}
@@ -0,0 +1,17 @@
1
+ import { createZodDto } from 'nestjs-zod'
2
+ import { RegisterRequest } from 'shared/types'
3
+ import { ACCOUNT_PATTERN } from 'shared/utils'
4
+ import z from 'zod'
5
+
6
+ export const registerSchema = z.object({
7
+ name: z.string().trim().min(2).max(100),
8
+ account: z
9
+ .string()
10
+ .trim()
11
+ .min(4)
12
+ .max(32)
13
+ .regex(ACCOUNT_PATTERN, '账号只能包含字母、数字和下划线'),
14
+ password: z.string().min(8).max(128),
15
+ }) satisfies z.ZodType<RegisterRequest>
16
+
17
+ export class RegisterDto extends createZodDto(registerSchema) {}
@@ -0,0 +1,50 @@
1
+ import { IS_PUBLIC_KEY } from '@/common/constants/auth'
2
+ import { AppException } from '@/common/exceptions/app.exception'
3
+ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
4
+ import { Reflector } from '@nestjs/core'
5
+ import { AUTH_ERRORS } from '../auth.errors'
6
+ import { SessionStoreService } from '../session/session-store.service'
7
+ import { SessionTokenService } from '../session/session-token.service'
8
+ import { AuthenticatedRequest } from '../session/session.types'
9
+
10
+ @Injectable()
11
+ export class SessionAuthGuard implements CanActivate {
12
+ constructor(
13
+ private readonly reflector: Reflector,
14
+ private readonly sessionTokenService: SessionTokenService,
15
+ private readonly sessionStoreService: SessionStoreService,
16
+ ) {}
17
+
18
+ async canActivate(context: ExecutionContext) {
19
+ const request = context.switchToHttp().getRequest<AuthenticatedRequest>()
20
+
21
+ // Cors 预检不携带业务 token , 也不会执行 Controller
22
+ if (request.method === 'OPTIONS') {
23
+ return true
24
+ }
25
+
26
+ const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
27
+ context.getHandler(),
28
+ context.getClass(),
29
+ ])
30
+
31
+ if (isPublic) {
32
+ return true
33
+ }
34
+
35
+ const rawToken = this.sessionTokenService.extractBearerToken(request.headers.authorization)
36
+
37
+ if (!rawToken) {
38
+ throw new AppException(AUTH_ERRORS.SESSION_TOKEN_MISSING)
39
+ }
40
+
41
+ const tokenDigest = this.sessionTokenService.digest(rawToken)
42
+ const auth = await this.sessionStoreService.validateAndTouch(tokenDigest)
43
+
44
+ if (!auth) {
45
+ throw new AppException(AUTH_ERRORS.SESSION_INVALID)
46
+ }
47
+ request.auth = auth
48
+ return true
49
+ }
50
+ }
@@ -0,0 +1,24 @@
1
+ import { Injectable } from '@nestjs/common'
2
+ import * as argon2 from 'argon2'
3
+
4
+ export const PASSWORD_HASH_OPTIONS = {
5
+ type: argon2.argon2id,
6
+ memoryCost: 65_536,
7
+ timeCost: 3,
8
+ parallelism: 4,
9
+ } as const
10
+
11
+ @Injectable()
12
+ export class PasswordService {
13
+ hash(password: string) {
14
+ return argon2.hash(password, PASSWORD_HASH_OPTIONS)
15
+ }
16
+
17
+ async verify(passswordHash: string, password: string) {
18
+ try {
19
+ return await argon2.verify(passswordHash, password)
20
+ } catch {
21
+ return false
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,198 @@
1
+ import {
2
+ createSessionKey,
3
+ createSessionSlotKey,
4
+ isSessionTerminal,
5
+ SESSION_KEY_PREFIX,
6
+ SESSION_SLOT_PREFIX,
7
+ SESSION_TERMINALS,
8
+ } from '@/common/constants/session.constants'
9
+ import { AppException } from '@/common/exceptions/app.exception'
10
+ import { InjectRedis } from '@nestjs-modules/ioredis'
11
+ import { Injectable } from '@nestjs/common'
12
+ import { ConfigService } from '@nestjs/config'
13
+ import Redis from 'ioredis'
14
+ import { AUTH_ERRORS } from '../auth.errors'
15
+ import {
16
+ CREATE_OR_REPLACE_SESSION_SCRIPT,
17
+ LOGOUT_SESSION_SCRIPT,
18
+ REVOKE_USER_SESSIONS_SCRIPT,
19
+ VALIDATE_AND_TOUCH_SESSION_SCRIPT,
20
+ } from './session.script'
21
+ import { CreatedSession, CreateSessionInput, CurrentAuthType } from './session.types'
22
+
23
+ const INVALID_SESSION_CODES = new Set(['NOT_FOUND', 'REPLACED', 'ABSOLUTE_EXPIRED'])
24
+
25
+ type SessionRedis = Redis & {
26
+ authCreateOrReplaceSession(...args: string[]): Promise<unknown>
27
+ authValidateAndTouchSession(...args: string[]): Promise<unknown>
28
+ authLogoutSession(...args: string[]): Promise<unknown>
29
+ authRevokeUserSessions(...args: string[]): Promise<unknown>
30
+ }
31
+
32
+ const SCRIPT_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,79}$/
33
+
34
+ function createScriptProtocolError(operation: string, value: string | undefined): Error {
35
+ const code = SCRIPT_CODE_PATTERN.test(value ?? '') ? value : 'UNKNOWN'
36
+ return new Error(`Redis ${operation} script returned ${code}`)
37
+ }
38
+
39
+ @Injectable()
40
+ export class SessionStoreService {
41
+ // private readonly logger = new Logger(SessionStoreService.name)
42
+ private readonly redis: SessionRedis
43
+ private readonly idleTtlMs: number
44
+ private readonly absoluteTtlMs: number
45
+
46
+ constructor(@InjectRedis() redis: Redis, config: ConfigService) {
47
+ this.redis = redis as SessionRedis
48
+ this.idleTtlMs = config.getOrThrow<number>('session.idleTtlMs')
49
+ this.absoluteTtlMs = config.getOrThrow<number>('session.absoluteTtlMs')
50
+
51
+ this.redis.defineCommand('authCreateOrReplaceSession', {
52
+ numberOfKeys: 2,
53
+ lua: CREATE_OR_REPLACE_SESSION_SCRIPT,
54
+ })
55
+
56
+ this.redis.defineCommand('authValidateAndTouchSession', {
57
+ numberOfKeys: 1,
58
+ lua: VALIDATE_AND_TOUCH_SESSION_SCRIPT,
59
+ })
60
+
61
+ this.redis.defineCommand('authLogoutSession', {
62
+ numberOfKeys: 1,
63
+ lua: LOGOUT_SESSION_SCRIPT,
64
+ })
65
+
66
+ this.redis.defineCommand('authRevokeUserSessions', {
67
+ numberOfKeys: SESSION_TERMINALS.length,
68
+ lua: REVOKE_USER_SESSIONS_SCRIPT,
69
+ })
70
+ }
71
+
72
+ private async execute(command: () => Promise<unknown>) {
73
+ try {
74
+ const result = await command()
75
+
76
+ if (!Array.isArray(result)) {
77
+ throw new Error('Redis script returned a non-array result')
78
+ }
79
+ return result.map((item) => String(item ?? ''))
80
+ } catch (cause: unknown) {
81
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, { cause })
82
+ }
83
+ }
84
+
85
+ async createOrReplace(input: CreateSessionInput): Promise<CreatedSession> {
86
+ const result = await this.execute(() =>
87
+ this.redis.authCreateOrReplaceSession(
88
+ createSessionSlotKey(input.userId, input.terminal),
89
+ createSessionKey(input.tokenDigest),
90
+ input.tokenDigest,
91
+ input.userId,
92
+ input.terminal,
93
+ String(this.idleTtlMs),
94
+ String(this.absoluteTtlMs),
95
+ input.loginIp,
96
+ input.userAgent,
97
+ SESSION_KEY_PREFIX,
98
+ ),
99
+ )
100
+
101
+ // if (result[0] !== '1') {
102
+ // this.logger.error(`Create session script rejected: ${result[1] ?? 'UNKNOWN'}`)
103
+ // throw new ServiceUnavailableException('暂时无法创建登录状态')
104
+ // }
105
+
106
+ const initialExpiresAtMs = Number(result[2])
107
+ const absoluteExpiresAtMs = Number(result[3])
108
+
109
+ // if (!Number.isFinite(initialExpiresAtMs) || !Number.isFinite(absoluteExpiresAtMs)) {
110
+ // throw new ServiceUnavailableException('登录状态数据异常')
111
+ // }
112
+
113
+ return {
114
+ initialExpiresAtMs,
115
+ absoluteExpiresAtMs,
116
+ }
117
+ }
118
+
119
+ async validateAndTouch(tokenDigest: string): Promise<CurrentAuthType | null> {
120
+ const result = await this.execute(() =>
121
+ this.redis.authValidateAndTouchSession(
122
+ createSessionKey(tokenDigest),
123
+ tokenDigest,
124
+ SESSION_SLOT_PREFIX,
125
+ String(this.idleTtlMs),
126
+ ),
127
+ )
128
+
129
+ if (result[0] !== '1') {
130
+ const code = result[1] ?? 'UNKNOWN'
131
+
132
+ if (INVALID_SESSION_CODES.has(code)) {
133
+ return null
134
+ }
135
+
136
+ // this.logger.error(`Validate session script returned unexpected code: ${code}`)
137
+ // throw new ServiceUnavailableException('登录状态数据异常')
138
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, {
139
+ cause: createScriptProtocolError('validateAndTouch', result[1]),
140
+ })
141
+ }
142
+
143
+ const userId = result[1]
144
+ const terminal = result[2]
145
+
146
+ // if (!userId || !terminal || !isSessionTerminal(terminal)) {
147
+ // this.logger.error('Redis returned an invalid Session payload')
148
+ // throw new ServiceUnavailableException('登录状态数据异常')
149
+ // }
150
+
151
+ if (!userId || !terminal || !isSessionTerminal(terminal)) {
152
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, {
153
+ cause: createScriptProtocolError('validateAndTouch', result[1]),
154
+ })
155
+ }
156
+
157
+ return {
158
+ userId,
159
+ terminal,
160
+ }
161
+ }
162
+
163
+ async logout(tokenDigest: string) {
164
+ const result = await this.execute(() =>
165
+ this.redis.authLogoutSession(createSessionKey(tokenDigest), tokenDigest, SESSION_SLOT_PREFIX),
166
+ )
167
+
168
+ // if (result[0] !== '1') {
169
+ // this.logger.error(`Logout session script returned unexpected code: ${result[1] ?? 'UNKNOWN'}`)
170
+ // throw new ServiceUnavailableException('暂时无法完成退出')
171
+ // }
172
+
173
+ if (result[0] !== '1') {
174
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, {
175
+ cause: createScriptProtocolError('logout', result[1]),
176
+ })
177
+ }
178
+ }
179
+
180
+ async revokeAllForUser(userId: string) {
181
+ const slotKeys = SESSION_TERMINALS.map((terminal) => createSessionSlotKey(userId, terminal))
182
+ const result = await this.execute(() =>
183
+ this.redis.authRevokeUserSessions(...slotKeys, SESSION_KEY_PREFIX),
184
+ )
185
+ // if (result[0] !== '1') {
186
+ // this.logger.error(
187
+ // `Revoke user sessions script returned unexpected code: ${result[1] ?? 'UNKNOWN'}`,
188
+ // )
189
+ // throw new ServiceUnavailableException('暂时无法撤销用户登录状态')
190
+ // }
191
+
192
+ if (result[0] !== '1') {
193
+ throw new AppException(AUTH_ERRORS.SERVICE_UNAVAILABLE, {
194
+ cause: createScriptProtocolError('revokeAllForUser', result[1]),
195
+ })
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,29 @@
1
+ import { Injectable } from '@nestjs/common'
2
+ import { ConfigService } from '@nestjs/config'
3
+ import { createHmac, randomBytes } from 'node:crypto'
4
+
5
+ @Injectable()
6
+ export class SessionTokenService {
7
+ private readonly tokenPepper: string
8
+
9
+ constructor(config: ConfigService) {
10
+ this.tokenPepper = config.getOrThrow<string>('session.tokenPepper')
11
+ }
12
+
13
+ digest(rawToken: string) {
14
+ return createHmac('sha256', this.tokenPepper).update(rawToken).digest('hex')
15
+ }
16
+
17
+ createToken() {
18
+ const rawToken = randomBytes(32).toString('base64url')
19
+ return {
20
+ rawToken,
21
+ tokenDigest: this.digest(rawToken),
22
+ }
23
+ }
24
+
25
+ extractBearerToken(authorization: string | undefined) {
26
+ const matched = /^Bearer\s+([A-Za-z0-9_-]{43})$/.exec(authorization ?? '')
27
+ return matched?.[1] ?? null
28
+ }
29
+ }