@lenne.tech/nest-server 11.32.3 → 11.33.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 (262) hide show
  1. package/.claude/rules/architecture.md +16 -0
  2. package/.claude/rules/configurable-features.md +11 -3
  3. package/.claude/rules/testing.md +101 -1
  4. package/CLAUDE.md +5 -0
  5. package/FRAMEWORK-API.md +58 -3
  6. package/bin/migrate.js +13 -3
  7. package/dist/core/common/helpers/content-disposition.helper.d.ts +1 -0
  8. package/dist/core/common/helpers/content-disposition.helper.js +14 -0
  9. package/dist/core/common/helpers/content-disposition.helper.js.map +1 -0
  10. package/dist/core/common/helpers/file.helper.d.ts +21 -2
  11. package/dist/core/common/helpers/file.helper.js +69 -13
  12. package/dist/core/common/helpers/file.helper.js.map +1 -1
  13. package/dist/core/common/helpers/graceful-shutdown.helper.d.ts +2 -0
  14. package/dist/core/common/helpers/graceful-shutdown.helper.js +55 -0
  15. package/dist/core/common/helpers/graceful-shutdown.helper.js.map +1 -0
  16. package/dist/core/common/helpers/project-name.helper.d.ts +2 -0
  17. package/dist/core/common/helpers/project-name.helper.js +38 -0
  18. package/dist/core/common/helpers/project-name.helper.js.map +1 -0
  19. package/dist/core/common/interfaces/cron-job-config.interface.d.ts +1 -0
  20. package/dist/core/common/interfaces/server-options.interface.d.ts +39 -0
  21. package/dist/core/common/services/core-cron-jobs.initializer.d.ts +10 -0
  22. package/dist/core/common/services/core-cron-jobs.initializer.js +46 -0
  23. package/dist/core/common/services/core-cron-jobs.initializer.js.map +1 -0
  24. package/dist/core/common/services/core-cron-jobs.registry.d.ts +8 -0
  25. package/dist/core/common/services/core-cron-jobs.registry.js +12 -0
  26. package/dist/core/common/services/core-cron-jobs.registry.js.map +1 -0
  27. package/dist/core/common/services/core-cron-jobs.service.d.ts +37 -7
  28. package/dist/core/common/services/core-cron-jobs.service.js +278 -27
  29. package/dist/core/common/services/core-cron-jobs.service.js.map +1 -1
  30. package/dist/core/common/services/core-redis-pubsub.d.ts +20 -0
  31. package/dist/core/common/services/core-redis-pubsub.js +112 -0
  32. package/dist/core/common/services/core-redis-pubsub.js.map +1 -0
  33. package/dist/core/common/services/core-redis.service.d.ts +25 -0
  34. package/dist/core/common/services/core-redis.service.js +154 -0
  35. package/dist/core/common/services/core-redis.service.js.map +1 -0
  36. package/dist/core/common/services/core-s3.service.d.ts +51 -0
  37. package/dist/core/common/services/core-s3.service.js +261 -0
  38. package/dist/core/common/services/core-s3.service.js.map +1 -0
  39. package/dist/core/common/services/core-trust-proxy.initializer.d.ts +16 -0
  40. package/dist/core/common/services/core-trust-proxy.initializer.js +77 -0
  41. package/dist/core/common/services/core-trust-proxy.initializer.js.map +1 -0
  42. package/dist/core/common/services/rate-limit-store.d.ts +61 -0
  43. package/dist/core/common/services/rate-limit-store.js +235 -0
  44. package/dist/core/common/services/rate-limit-store.js.map +1 -0
  45. package/dist/core/modules/ai/core-ai-mcp.controller.d.ts +19 -3
  46. package/dist/core/modules/ai/core-ai-mcp.controller.js +148 -18
  47. package/dist/core/modules/ai/core-ai-mcp.controller.js.map +1 -1
  48. package/dist/core/modules/ai/services/core-ai.service.d.ts +7 -2
  49. package/dist/core/modules/ai/services/core-ai.service.js +24 -20
  50. package/dist/core/modules/ai/services/core-ai.service.js.map +1 -1
  51. package/dist/core/modules/auth/core-auth.module.js +4 -1
  52. package/dist/core/modules/auth/core-auth.module.js.map +1 -1
  53. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.d.ts +1 -1
  54. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js +3 -14
  55. package/dist/core/modules/auth/guards/legacy-auth-rate-limit.guard.js.map +1 -1
  56. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.d.ts +12 -10
  57. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js +30 -59
  58. package/dist/core/modules/auth/services/legacy-auth-rate-limiter.service.js.map +1 -1
  59. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +8 -2
  60. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +118 -45
  61. package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -1
  62. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.d.ts +1 -1
  63. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js +2 -11
  64. package/dist/core/modules/better-auth/core-better-auth-rate-limit.middleware.js.map +1 -1
  65. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.d.ts +12 -10
  66. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js +30 -85
  67. package/dist/core/modules/better-auth/core-better-auth-rate-limiter.service.js.map +1 -1
  68. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +0 -1
  69. package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
  70. package/dist/core/modules/file/core-file.controller.d.ts +15 -3
  71. package/dist/core/modules/file/core-file.controller.js +90 -20
  72. package/dist/core/modules/file/core-file.controller.js.map +1 -1
  73. package/dist/core/modules/file/core-file.resolver.d.ts +4 -4
  74. package/dist/core/modules/file/core-file.resolver.js +23 -16
  75. package/dist/core/modules/file/core-file.resolver.js.map +1 -1
  76. package/dist/core/modules/file/core-file.service.d.ts +41 -7
  77. package/dist/core/modules/file/core-file.service.js +227 -7
  78. package/dist/core/modules/file/core-file.service.js.map +1 -1
  79. package/dist/core/modules/file/file-metadata.helper.d.ts +16 -0
  80. package/dist/core/modules/file/file-metadata.helper.js +33 -0
  81. package/dist/core/modules/file/file-metadata.helper.js.map +1 -0
  82. package/dist/core/modules/file/file-roles.helper.d.ts +4 -0
  83. package/dist/core/modules/file/file-roles.helper.js +53 -0
  84. package/dist/core/modules/file/file-roles.helper.js.map +1 -0
  85. package/dist/core/modules/file/file-storage.helper.d.ts +13 -0
  86. package/dist/core/modules/file/file-storage.helper.js +65 -0
  87. package/dist/core/modules/file/file-storage.helper.js.map +1 -0
  88. package/dist/core/modules/file/filesystem-file.helper.d.ts +22 -0
  89. package/dist/core/modules/file/filesystem-file.helper.js +77 -0
  90. package/dist/core/modules/file/filesystem-file.helper.js.map +1 -0
  91. package/dist/core/modules/file/interfaces/file-service-options.interface.d.ts +1 -0
  92. package/dist/core/modules/file/interfaces/file-upload.interface.d.ts +7 -1
  93. package/dist/core/modules/file/s3-file.helper.d.ts +37 -0
  94. package/dist/core/modules/file/s3-file.helper.js +81 -0
  95. package/dist/core/modules/file/s3-file.helper.js.map +1 -0
  96. package/dist/core/modules/hub/core-hub.controller.d.ts +8 -8
  97. package/dist/core/modules/hub/core-hub.controller.js +24 -22
  98. package/dist/core/modules/hub/core-hub.controller.js.map +1 -1
  99. package/dist/core/modules/hub/helpers/hub-mask.helper.js +2 -2
  100. package/dist/core/modules/hub/helpers/hub-mask.helper.js.map +1 -1
  101. package/dist/core/modules/hub/hub-buffer.d.ts +23 -0
  102. package/dist/core/modules/hub/hub-buffer.js +101 -0
  103. package/dist/core/modules/hub/hub-buffer.js.map +1 -0
  104. package/dist/core/modules/hub/services/core-hub-mailbox.service.d.ts +7 -5
  105. package/dist/core/modules/hub/services/core-hub-mailbox.service.js +14 -10
  106. package/dist/core/modules/hub/services/core-hub-mailbox.service.js.map +1 -1
  107. package/dist/core/modules/hub/services/hub-log-buffer.service.d.ts +5 -2
  108. package/dist/core/modules/hub/services/hub-log-buffer.service.js +17 -8
  109. package/dist/core/modules/hub/services/hub-log-buffer.service.js.map +1 -1
  110. package/dist/core/modules/hub/services/hub-query-profiler.service.d.ts +4 -2
  111. package/dist/core/modules/hub/services/hub-query-profiler.service.js +13 -7
  112. package/dist/core/modules/hub/services/hub-query-profiler.service.js.map +1 -1
  113. package/dist/core/modules/hub/services/hub-trace-buffer.service.d.ts +4 -2
  114. package/dist/core/modules/hub/services/hub-trace-buffer.service.js +13 -8
  115. package/dist/core/modules/hub/services/hub-trace-buffer.service.js.map +1 -1
  116. package/dist/core/modules/migrate/cli/migrate-cli.d.ts +3 -1
  117. package/dist/core/modules/migrate/cli/migrate-cli.js +29 -4
  118. package/dist/core/modules/migrate/cli/migrate-cli.js.map +1 -1
  119. package/dist/core/modules/migrate/helpers/migration.helper.d.ts +3 -6
  120. package/dist/core/modules/migrate/helpers/migration.helper.js +55 -8
  121. package/dist/core/modules/migrate/helpers/migration.helper.js.map +1 -1
  122. package/dist/core/modules/migrate/migration-runner.d.ts +1 -0
  123. package/dist/core/modules/migrate/migration-runner.js +4 -0
  124. package/dist/core/modules/migrate/migration-runner.js.map +1 -1
  125. package/dist/core/modules/migrate/mongo-state-store.d.ts +1 -0
  126. package/dist/core/modules/migrate/mongo-state-store.js +98 -9
  127. package/dist/core/modules/migrate/mongo-state-store.js.map +1 -1
  128. package/dist/core/modules/system-setup/core-system-setup.service.d.ts +2 -0
  129. package/dist/core/modules/system-setup/core-system-setup.service.js +31 -1
  130. package/dist/core/modules/system-setup/core-system-setup.service.js.map +1 -1
  131. package/dist/core/modules/tenant/core-tenant.guard.d.ts +17 -3
  132. package/dist/core/modules/tenant/core-tenant.guard.js +92 -15
  133. package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
  134. package/dist/core/modules/tenant/core-tenant.module.js +3 -4
  135. package/dist/core/modules/tenant/core-tenant.module.js.map +1 -1
  136. package/dist/core/modules/tus/core-tus.controller.d.ts +2 -0
  137. package/dist/core/modules/tus/core-tus.controller.js +29 -3
  138. package/dist/core/modules/tus/core-tus.controller.js.map +1 -1
  139. package/dist/core/modules/tus/core-tus.service.d.ts +24 -1
  140. package/dist/core/modules/tus/core-tus.service.js +182 -28
  141. package/dist/core/modules/tus/core-tus.service.js.map +1 -1
  142. package/dist/core/modules/tus/interfaces/tus-config.interface.js +3 -0
  143. package/dist/core/modules/tus/interfaces/tus-config.interface.js.map +1 -1
  144. package/dist/core/modules/tus/tus-redis-locker.d.ts +25 -0
  145. package/dist/core/modules/tus/tus-redis-locker.js +87 -0
  146. package/dist/core/modules/tus/tus-redis-locker.js.map +1 -0
  147. package/dist/core/modules/tus/tus.module.d.ts +1 -0
  148. package/dist/core/modules/tus/tus.module.js +30 -4
  149. package/dist/core/modules/tus/tus.module.js.map +1 -1
  150. package/dist/core.module.js +18 -1
  151. package/dist/core.module.js.map +1 -1
  152. package/dist/index.d.ts +10 -0
  153. package/dist/index.js +10 -0
  154. package/dist/index.js.map +1 -1
  155. package/dist/main.js +2 -1
  156. package/dist/main.js.map +1 -1
  157. package/dist/server/modules/file/file.controller.js.map +1 -1
  158. package/dist/server/modules/file/file.resolver.js +1 -12
  159. package/dist/server/modules/file/file.resolver.js.map +1 -1
  160. package/dist/server/modules/file/file.service.d.ts +5 -1
  161. package/dist/server/modules/file/file.service.js +12 -3
  162. package/dist/server/modules/file/file.service.js.map +1 -1
  163. package/dist/server/modules/user/avatar.controller.d.ts +5 -1
  164. package/dist/server/modules/user/avatar.controller.js +26 -10
  165. package/dist/server/modules/user/avatar.controller.js.map +1 -1
  166. package/dist/server/modules/user/user.model.js +3 -2
  167. package/dist/server/modules/user/user.model.js.map +1 -1
  168. package/dist/server/modules/user/user.module.js +6 -2
  169. package/dist/server/modules/user/user.module.js.map +1 -1
  170. package/dist/server/modules/user/user.service.d.ts +1 -1
  171. package/dist/server/modules/user/user.service.js +6 -13
  172. package/dist/server/modules/user/user.service.js.map +1 -1
  173. package/dist/tsconfig.build.tsbuildinfo +1 -1
  174. package/docs/REQUEST-LIFECYCLE.md +57 -8
  175. package/docs/security-overrides.md +30 -5
  176. package/migration-guides/11.32.3-to-11.32.4.md +335 -0
  177. package/migration-guides/11.32.x-to-11.33.x.md +1551 -0
  178. package/package.json +74 -42
  179. package/src/core/common/helpers/content-disposition.helper.ts +90 -0
  180. package/src/core/common/helpers/file.helper.ts +201 -20
  181. package/src/core/common/helpers/graceful-shutdown.helper.ts +116 -0
  182. package/src/core/common/helpers/project-name.helper.ts +71 -0
  183. package/src/core/common/interfaces/cron-job-config.interface.ts +16 -0
  184. package/src/core/common/interfaces/server-options.interface.ts +439 -2
  185. package/src/core/common/services/core-cron-jobs.initializer.ts +39 -0
  186. package/src/core/common/services/core-cron-jobs.registry.ts +45 -0
  187. package/src/core/common/services/core-cron-jobs.service.ts +589 -47
  188. package/src/core/common/services/core-redis-pubsub.ts +183 -0
  189. package/src/core/common/services/core-redis.service.ts +249 -0
  190. package/src/core/common/services/core-s3.service.ts +465 -0
  191. package/src/core/common/services/core-trust-proxy.initializer.ts +112 -0
  192. package/src/core/common/services/rate-limit-store.ts +492 -0
  193. package/src/core/modules/ai/README.md +25 -0
  194. package/src/core/modules/ai/core-ai-mcp.controller.ts +253 -22
  195. package/src/core/modules/ai/services/core-ai.service.ts +33 -24
  196. package/src/core/modules/auth/core-auth.module.ts +7 -1
  197. package/src/core/modules/auth/guards/legacy-auth-rate-limit.guard.ts +12 -21
  198. package/src/core/modules/auth/services/legacy-auth-rate-limiter.service.ts +49 -93
  199. package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +202 -66
  200. package/src/core/modules/better-auth/core-better-auth-rate-limit.middleware.ts +11 -18
  201. package/src/core/modules/better-auth/core-better-auth-rate-limiter.service.ts +51 -132
  202. package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +7 -1
  203. package/src/core/modules/file/INTEGRATION-CHECKLIST.md +108 -0
  204. package/src/core/modules/file/README.md +324 -41
  205. package/src/core/modules/file/core-file.controller.ts +267 -19
  206. package/src/core/modules/file/core-file.resolver.ts +46 -12
  207. package/src/core/modules/file/core-file.service.ts +482 -16
  208. package/src/core/modules/file/file-metadata.helper.ts +142 -0
  209. package/src/core/modules/file/file-roles.helper.ts +110 -0
  210. package/src/core/modules/file/file-storage.helper.ts +163 -0
  211. package/src/core/modules/file/filesystem-file.helper.ts +184 -0
  212. package/src/core/modules/file/interfaces/file-service-options.interface.ts +14 -0
  213. package/src/core/modules/file/interfaces/file-upload.interface.ts +17 -1
  214. package/src/core/modules/file/s3-file.helper.ts +209 -0
  215. package/src/core/modules/hub/README.md +48 -0
  216. package/src/core/modules/hub/core-hub.controller.ts +18 -16
  217. package/src/core/modules/hub/helpers/hub-mask.helper.ts +21 -4
  218. package/src/core/modules/hub/hub-buffer.ts +198 -0
  219. package/src/core/modules/hub/services/core-hub-mailbox.service.ts +22 -12
  220. package/src/core/modules/hub/services/hub-log-buffer.service.ts +24 -8
  221. package/src/core/modules/hub/services/hub-query-profiler.service.ts +9 -6
  222. package/src/core/modules/hub/services/hub-trace-buffer.service.ts +14 -9
  223. package/src/core/modules/migrate/README.md +68 -6
  224. package/src/core/modules/migrate/cli/migrate-cli.ts +69 -6
  225. package/src/core/modules/migrate/helpers/migration.helper.ts +144 -10
  226. package/src/core/modules/migrate/migration-runner.ts +10 -1
  227. package/src/core/modules/migrate/mongo-state-store.ts +179 -11
  228. package/src/core/modules/system-setup/README.md +7 -2
  229. package/src/core/modules/system-setup/core-system-setup.service.ts +95 -1
  230. package/src/core/modules/tenant/README.md +21 -1
  231. package/src/core/modules/tenant/core-tenant.guard.ts +135 -14
  232. package/src/core/modules/tenant/core-tenant.module.ts +8 -4
  233. package/src/core/modules/tus/INTEGRATION-CHECKLIST.md +93 -21
  234. package/src/core/modules/tus/README.md +35 -2
  235. package/src/core/modules/tus/core-tus.controller.ts +64 -12
  236. package/src/core/modules/tus/core-tus.service.ts +358 -34
  237. package/src/core/modules/tus/interfaces/tus-config.interface.ts +9 -0
  238. package/src/core/modules/tus/tus-redis-locker.ts +139 -0
  239. package/src/core/modules/tus/tus.module.ts +78 -6
  240. package/src/core.module.ts +26 -1
  241. package/src/index.ts +10 -0
  242. package/src/main.ts +7 -1
  243. package/src/server/modules/file/file.controller.ts +7 -3
  244. package/src/server/modules/file/file.resolver.ts +5 -17
  245. package/src/server/modules/file/file.service.ts +42 -3
  246. package/src/server/modules/user/avatar.controller.ts +39 -9
  247. package/src/server/modules/user/user.model.ts +9 -3
  248. package/src/server/modules/user/user.module.ts +12 -3
  249. package/src/server/modules/user/user.service.ts +20 -17
  250. package/src/test/README.md +7 -2
  251. package/src/core/common/helpers/logging.helper.spec.ts +0 -61
  252. package/src/core/common/helpers/process-diagnostics.helper.spec.ts +0 -310
  253. package/src/core/common/services/brevo.service.spec.ts +0 -266
  254. package/src/core/modules/hub/core-hub.module.spec.ts +0 -108
  255. package/src/core/modules/hub/helpers/hub-command-shape.helper.spec.ts +0 -48
  256. package/src/core/modules/hub/helpers/hub-mask.helper.spec.ts +0 -67
  257. package/src/core/modules/hub/helpers/hub-mermaid.helper.spec.ts +0 -61
  258. package/src/core/modules/hub/helpers/hub-shell.helper.spec.ts +0 -106
  259. package/src/core/modules/hub/hub-config.helper.spec.ts +0 -108
  260. package/src/core/modules/hub/hub-ring-buffer.spec.ts +0 -95
  261. package/src/core/modules/hub/services/core-hub-mailbox.service.spec.ts +0 -116
  262. package/src/core/modules/hub/services/hub-trace-buffer.service.spec.ts +0 -112
@@ -75,10 +75,14 @@ export class CoreTenantModule {
75
75
  provide: CoreTenantService,
76
76
  useClass: Service,
77
77
  },
78
- {
79
- provide: APP_GUARD,
80
- useClass: Guard,
81
- },
78
+ // Registered under its OWN token first, then aliased to APP_GUARD — the pattern
79
+ // CoreAuthModule uses for RolesGuard. A bare `{ provide: APP_GUARD, useClass: Guard }`
80
+ // makes Nest mint an internal token for the instance, so `@Optional() CoreTenantGuard`
81
+ // in CoreTenantService resolves to undefined and every invalidateUser()/invalidateAll()
82
+ // — including the cross-replica Redis broadcast — silently did nothing.
83
+ Guard,
84
+ ...(Guard === CoreTenantGuard ? [] : [{ provide: CoreTenantGuard, useExisting: Guard }]),
85
+ { provide: APP_GUARD, useExisting: Guard },
82
86
  ];
83
87
 
84
88
  // When a custom model name is used, alias the default injection token to the custom model.
@@ -2,18 +2,61 @@
2
2
 
3
3
  **For customizing TUS uploads in projects using `@lenne.tech/nest-server`.**
4
4
 
5
- > **Note:** TUS is **enabled by default** with no configuration needed. This checklist is only for projects that need to customize behavior (e.g., require authentication).
5
+ > **Note:** TUS is **enabled by default** with no configuration needed. This checklist is only for projects that need to customize behavior (e.g., widen or narrow who may upload).
6
+
7
+ > **Since 11.33.0 uploads require a signed-in user.** `tus.roles` defaults to `[RoleEnum.S_USER]`; it used to be `S_EVERYONE`. If your project accepts attachments on a public form, you must opt back in explicitly — see Step 0.
6
8
 
7
9
  ---
8
10
 
9
11
  ## Do You Need This Checklist?
10
12
 
11
- | Scenario | Checklist Needed? |
12
- | -------------------------------------------- | ---------------------------------------------------- |
13
- | Use TUS with defaults (everyone can upload) | No - works automatically |
14
- | Require authentication for uploads | Yes - Step 1 |
15
- | Custom upload handling (notifications, etc.) | Yes - Step 2 |
16
- | Disable TUS completely | No - just use `TusModule.forRoot({ config: false })` |
13
+ | Scenario | Checklist Needed? |
14
+ | -------------------------------------------------- | ---------------------------------------------------- |
15
+ | Use TUS with defaults (signed-in users may upload) | No - works automatically |
16
+ | **Allow anonymous uploads** (public form) | **Yes - Step 0** (the default no longer allows this) |
17
+ | Restrict uploads to specific roles | Yes - Step 0 |
18
+ | Custom upload handling (notifications, etc.) | Yes - Step 2 |
19
+ | Hard-code a policy that config must NOT change | Yes - Step 1 |
20
+ | Disable TUS completely | No - just use `TusModule.forRoot({ config: false })` |
21
+
22
+ ---
23
+
24
+ ## Step 0: Configure `tus.roles` (Who May Upload)
25
+
26
+ This is the normal way to set TUS permissions — no controller subclass needed.
27
+
28
+ ```typescript
29
+ // src/config.env.ts
30
+ tus: {
31
+ roles: [RoleEnum.S_USER], // default since 11.33.0
32
+ // roles: [RoleEnum.S_EVERYONE], // opt back in to anonymous uploads (public form)
33
+ // roles: ['editor', 'contributor'], // project-specific roles work too
34
+ },
35
+ ```
36
+
37
+ Plain role **strings**, exactly like a hand-written `@Roles()` call, so project roles work alongside
38
+ the `RoleEnum` system roles.
39
+
40
+ **Three properties worth knowing:**
41
+
42
+ 1. **`roles: []` is rejected**, with a warning, and the default applies. An all-empty role set reads
43
+ to the guards as "no roles required" and returns `true` — honouring it literally would _open_ the
44
+ endpoints, the exact opposite of what someone writing `[]` intends. Same for a non-array or an
45
+ array holding a non-string.
46
+
47
+ 2. **`OPTIONS` stays public regardless.** `handleTusOptions` / `handleTusOptionsWithId` keep their
48
+ own `@Roles(RoleEnum.S_EVERYONE)`, because that is the CORS preflight — browsers send it
49
+ **without credentials**, and it returns server capabilities only. Gating it would make every
50
+ browser upload fail before the first byte.
51
+
52
+ 3. **Pair it with the file roles.** A TUS upload lands in the same store `file.downloadRoles`
53
+ guards (default `[RoleEnum.ADMIN]`), so with both at their defaults a signed-in user may upload
54
+ but cannot read the result back. Either widen `file.downloadRoles`, or write an owner into the
55
+ metadata at upload time and authorize per file in `CoreFileService.checkRights()`. TUS uploads
56
+ already carry `metadata.tusUploadId` plus the original TUS metadata.
57
+
58
+ **Verify:** `curl -X OPTIONS <baseUrl>/tus` still answers `204` with the TUS capability headers,
59
+ while `POST /tus` without a token answers `401`.
17
60
 
18
61
  ---
19
62
 
@@ -30,22 +73,42 @@ https://github.com/lenneTech/nest-server/tree/develop/src/server
30
73
 
31
74
  ---
32
75
 
33
- ## Step 1: Custom Controller (Require Authentication)
76
+ ## Step 1: Custom Controller (Custom Upload Logic)
77
+
78
+ > Only needed for custom handler behaviour. For "who may upload", use Step 0 — it needs no subclass.
34
79
 
35
80
  **Create:** `src/server/modules/tus/tus.controller.ts`
36
81
 
37
82
  ```typescript
38
83
  import { Controller } from '@nestjs/common';
39
- import { CoreTusController, Roles, RoleEnum } from '@lenne.tech/nest-server';
84
+ import { CoreTusController } from '@lenne.tech/nest-server';
40
85
 
41
86
  @Controller('tus')
42
- @Roles(RoleEnum.S_USER) // Require authenticated user
43
87
  export class TusController extends CoreTusController {
44
- // All methods inherit the S_USER requirement
45
- // Override methods here for custom logic
88
+ // Inherits the handlers; `tus.roles` from config applies to them.
89
+ // Override methods here for custom logic.
46
90
  }
47
91
  ```
48
92
 
93
+ **How `tus.roles` reaches a custom controller — and why you should not fight it with `@Roles()`:**
94
+
95
+ `TusModule.forRoot()` calls `applyRoles()` on the class it actually registers, writing the configured
96
+ roles onto that class **and** onto its `handleTus` / `handleTusWithId`, resolved through the
97
+ prototype chain. `forRoot()` runs after the controller's decorators have been evaluated, so it wins:
98
+
99
+ | You write on the subclass | Effective at runtime |
100
+ | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
101
+ | Nothing | `tus.roles` (config) |
102
+ | A class-level `@Roles(...)` | `tus.roles` — overwritten by `applyRoles()` |
103
+ | A re-declared `handleTus` with its own `@Roles(...)` | `tus.roles` — overwritten too, because `applyRoles()` writes onto whichever function `prototype.handleTus` resolves to |
104
+
105
+ So `tus.roles` is the single source of truth for TUS permissions. If you need a policy that
106
+ configuration cannot change, either give your handler a **different method name** (`applyRoles()`
107
+ only targets `handleTus` / `handleTusWithId`), or register the controller outside `TusModule`.
108
+
109
+ > This differs from the file module, where `applyFileRoles()` targets the **base** class methods by
110
+ > name — there an override genuinely does opt out.
111
+
49
112
  **Update ServerModule:**
50
113
 
51
114
  ```typescript
@@ -104,6 +167,10 @@ export class TusService extends CoreTusService {
104
167
  path: '/tus',
105
168
  maxSize: 50 * 1024 * 1024 * 1024, // 50 GB
106
169
  expiration: { expiresIn: '24h' },
170
+ roles: [RoleEnum.S_USER], // since 11.33.0 (was S_EVERYONE)
171
+ // s3Staging: true when `s3` is configured — stages in-progress uploads in
172
+ // s3.stagingBucket instead of on pod-local disk, so resumable uploads survive
173
+ // replica restarts and need no sticky sessions. Set false to force local disk.
107
174
  }
108
175
  ```
109
176
 
@@ -132,20 +199,25 @@ TusModule.forRoot({ config: false });
132
199
 
133
200
  - [ ] `npm run build` succeeds
134
201
  - [ ] `npm test` passes
135
- - [ ] `OPTIONS /tus` returns TUS capabilities
136
- - [ ] Upload via tus-js-client works
137
- - [ ] File appears in GridFS after upload completion
138
- - [ ] (If customized) Authentication is required for uploads
202
+ - [ ] `OPTIONS /tus` returns TUS capabilities **without a token** (the CORS preflight must stay public)
203
+ - [ ] `POST /tus` **without** a token answers `401` (or `204` if you deliberately set `roles: [S_EVERYONE]`)
204
+ - [ ] Upload via tus-js-client works for a user holding a role from `tus.roles`
205
+ - [ ] File appears in the configured file store after upload completion (GridFS by default; S3 or filesystem per `file.storage`)
206
+ - [ ] The uploaded file can also be **downloaded** by its intended audience — check `file.downloadRoles` too
139
207
 
140
208
  ---
141
209
 
142
210
  ## Common Mistakes
143
211
 
144
- | Mistake | Symptom | Fix |
145
- | ------------------------------------ | ------------------------------ | ------------------------------------------------- |
146
- | Forgot to register custom controller | Default S_EVERYONE permissions | Add `controller: TusController` to forRoot() |
147
- | Custom controller missing @Roles | No authentication required | Add `@Roles(RoleEnum.S_USER)` to controller class |
148
- | Using wrong endpoint path | 404 on upload | Ensure client uses same path as config |
212
+ | Mistake | Symptom | Fix |
213
+ | -------------------------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
214
+ | Assuming uploads are still public (pre-11.33.0) | `401` on the very first `POST /tus` from a public form | `tus: { roles: [RoleEnum.S_EVERYONE] }` Step 0 |
215
+ | `tus: { roles: [] }` meaning "nobody" | Warning in the log, default `[S_USER]` applied instead | An empty role set would OPEN the routes; name the roles explicitly — Step 0 |
216
+ | Gating `OPTIONS` in a custom controller | Every browser upload fails before the first byte | Leave `handleTusOptions*` on `@Roles(RoleEnum.S_EVERYONE)` the preflight sends no credentials |
217
+ | Expecting a subclass `@Roles()` to win over config | Roles look ignored | `applyRoles()` overwrites them — Step 1 table |
218
+ | Forgot to register custom controller | Custom handler logic never runs (core controller is used) | Add `controller: TusController` to `forRoot()` |
219
+ | Upload succeeds but the file cannot be downloaded | `403` on `GET /files/id/:id` | `file.downloadRoles` defaults to `[ADMIN]` — widen it, or authorize per file in `checkRights()` |
220
+ | Using wrong endpoint path | 404 on upload | Ensure client uses same path as config |
149
221
 
150
222
  ---
151
223
 
@@ -262,7 +262,31 @@ upload.start(); // Resumes from where it left off
262
262
 
263
263
  ### Require Authentication
264
264
 
265
- By default, TUS allows everyone (`S_EVERYONE`) to upload. To require authentication, create a custom controller:
265
+ Since 11.33.0 TUS requires a signed-in caller by default (`tus.roles`, default `[S_USER]`). The
266
+ previous default was `S_EVERYONE`, which let anonymous callers write into — and, with the
267
+ termination extension, delete from — the same GridFS bucket the download routes guard.
268
+
269
+ Set it in `config.env.ts`:
270
+
271
+ ```typescript
272
+ tus: {
273
+ roles: [RoleEnum.S_USER];
274
+ } // default
275
+ tus: {
276
+ roles: [RoleEnum.S_EVERYONE];
277
+ } // opt back in to anonymous uploads
278
+ tus: {
279
+ roles: ['editor', 'contributor'];
280
+ } // project-specific roles work too
281
+ ```
282
+
283
+ `roles: []` is rejected with a warning rather than honoured — an all-empty role set reads to the
284
+ guards as "no roles required" and would open the endpoints instead of closing them.
285
+
286
+ `OPTIONS` stays public regardless: it is the CORS preflight, which browsers send without
287
+ credentials, and it discloses only server capabilities.
288
+
289
+ Alternatively, create a custom controller:
266
290
 
267
291
  ```typescript
268
292
  // src/server/modules/tus/tus.controller.ts
@@ -332,7 +356,7 @@ GET /files/:filename
332
356
 
333
357
  # Via GraphQL
334
358
  query {
335
- file(id: "...") {
359
+ getFileInfo(filename: "...") {
336
360
  id
337
361
  filename
338
362
  contentType
@@ -343,6 +367,15 @@ query {
343
367
 
344
368
  **Recommendation:** Use the ID-based endpoint (`/files/id/:id`) for TUS uploads as filenames may not be unique.
345
369
 
370
+ > **These download routes are gated.** They require `file.downloadRoles`, which defaults to
371
+ > `[RoleEnum.ADMIN]` — while uploading here only requires `tus.roles` (default `S_USER`). So out of
372
+ > the box a signed-in user can upload but cannot read their own file back.
373
+ >
374
+ > Do not fix that by widening `downloadRoles` to `S_USER`: that would let every signed-in user read
375
+ > _every_ file in the shared bucket. Write an owner into the file metadata at upload time and
376
+ > authorize per file in `CoreFileService.checkRights()` — see the File module's README, section
377
+ > "Access control".
378
+
346
379
  ### File Metadata
347
380
 
348
381
  The following metadata is stored with each GridFS file:
@@ -1,46 +1,98 @@
1
- import { All, Controller, Logger, Req, Res } from '@nestjs/common';
1
+ import { All, Controller, Logger, Options, Req, Res } from '@nestjs/common';
2
2
  import { Request, Response } from 'express';
3
3
 
4
4
  import { Roles } from '../../common/decorators/roles.decorator';
5
5
  import { RoleEnum } from '../../common/enums/role.enum';
6
+ import { SkipTenantCheck } from '../tenant/core-tenant.decorators';
6
7
  import { CoreTusService } from './core-tus.service';
7
8
 
8
9
  /**
9
10
  * Core TUS Controller
10
11
  *
11
12
  * Handles all TUS protocol requests and delegates to the @tus/server handler.
12
- * This controller uses S_EVERYONE by default, allowing all users to upload.
13
13
  *
14
- * Projects can extend this controller to add authentication/authorization:
14
+ * SECURITY: requires a signed-in caller (`S_USER`) by default. Configure via
15
+ * `tus: { roles: [...] }` — `TusModule.forRoot()` writes the configured roles
16
+ * onto these handlers at module-build time, so the decorators below are the
17
+ * fallback for a controller built outside that path, not the effective value.
18
+ *
19
+ * The default used to be `S_EVERYONE`, which the roles guard turns into an
20
+ * unconditional `return true` WITHOUT authenticating. Since a tus upload writes
21
+ * into the same shared GridFS bucket the file download routes read from, and
22
+ * the termination extension is on by default, that meant anonymous callers
23
+ * could fill — and delete from — a store only privileged callers may read.
24
+ *
25
+ * Projects that genuinely need anonymous uploads should say so explicitly:
15
26
  *
16
27
  * @example
17
28
  * ```typescript
18
- * @Controller('tus')
19
- * @Roles(RoleEnum.S_USER) // Require authentication
20
- * export class TusController extends CoreTusController {
21
- * // Customize as needed
22
- * }
29
+ * // config.env.ts
30
+ * tus: { roles: [RoleEnum.S_EVERYONE] }
23
31
  * ```
24
32
  */
33
+ /**
34
+ * TENANT SCOPING: the same reasoning as `CoreFileController` — a tus upload lands in
35
+ * the SAME store the file routes read, and that store is reached through the native
36
+ * driver, so `mongooseTenantPlugin` never scopes it. Without this decorator a
37
+ * configured non-system `tus.roles` (e.g. `['admin']`) is resolved against
38
+ * `membership.role`, which would let a workspace "admin" of ANY tenant write into —
39
+ * and, via the termination extension, delete from — a store that has no tenant
40
+ * boundary at all.
41
+ */
25
42
  @Controller('tus')
26
- @Roles(RoleEnum.S_EVERYONE)
43
+ @Roles(RoleEnum.S_USER)
44
+ @SkipTenantCheck()
27
45
  export class CoreTusController {
28
46
  private readonly logger = new Logger(CoreTusController.name);
29
47
 
30
48
  constructor(protected readonly tusService: CoreTusService) {}
31
49
 
50
+ /**
51
+ * Answer the tus discovery / CORS preflight request.
52
+ *
53
+ * DELIBERATELY UNAUTHENTICATED, and it must stay that way: a browser sends the
54
+ * CORS preflight WITHOUT credentials, by specification. Gating OPTIONS behind
55
+ * `tus.roles` would make every browser upload fail at the preflight — the
56
+ * endpoint would be unreachable from a web client no matter how valid the
57
+ * caller's session is.
58
+ *
59
+ * Nothing is disclosed: the response carries only server capabilities
60
+ * (`Tus-Version`, `Tus-Extension`, `Tus-Max-Size`), never upload data or the
61
+ * existence of any particular upload. `roles` is not applied to this handler.
62
+ *
63
+ * Declared before the `@All()` handlers so it wins the route match.
64
+ */
65
+ @Options()
66
+ @Roles(RoleEnum.S_EVERYONE)
67
+ async handleTusOptions(@Req() req: Request, @Res() res: Response): Promise<void> {
68
+ return this.handleTus(req, res);
69
+ }
70
+
71
+ /**
72
+ * Answer the tus discovery / CORS preflight for an upload-specific URL.
73
+ *
74
+ * Same reasoning as {@link handleTusOptions}. `@tus/server` answers OPTIONS
75
+ * identically regardless of the id, so this leaks no information about whether
76
+ * the upload exists.
77
+ */
78
+ @Options(':id')
79
+ @Roles(RoleEnum.S_EVERYONE)
80
+ async handleTusOptionsWithId(@Req() req: Request, @Res() res: Response): Promise<void> {
81
+ return this.handleTus(req, res);
82
+ }
83
+
32
84
  /**
33
85
  * Handle all TUS protocol requests
34
86
  *
35
87
  * The @tus/server handles:
36
- * - OPTIONS: Return server capabilities
88
+ * - OPTIONS: Return server capabilities (see handleTusOptions — routed separately)
37
89
  * - POST: Create new upload
38
90
  * - HEAD: Get upload status/offset
39
91
  * - PATCH: Continue upload
40
92
  * - DELETE: Terminate upload (if termination extension enabled)
41
93
  */
42
94
  @All()
43
- @Roles(RoleEnum.S_EVERYONE)
95
+ @Roles(RoleEnum.S_USER)
44
96
  async handleTus(@Req() req: Request, @Res() res: Response): Promise<void> {
45
97
  const server = this.tusService.getServer();
46
98
 
@@ -66,7 +118,7 @@ export class CoreTusController {
66
118
  * Routes like /tus/:id for HEAD, PATCH, DELETE
67
119
  */
68
120
  @All(':id')
69
- @Roles(RoleEnum.S_EVERYONE)
121
+ @Roles(RoleEnum.S_USER)
70
122
  async handleTusWithId(@Req() req: Request, @Res() res: Response): Promise<void> {
71
123
  const server = this.tusService.getServer();
72
124