@venizia/ignis 0.2.0-9 → 0.2.1-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 (400) hide show
  1. package/LICENSE.md +0 -1
  2. package/README.md +118 -158
  3. package/dist/base/applications/base.d.ts +19 -13
  4. package/dist/base/applications/base.d.ts.map +1 -1
  5. package/dist/base/applications/base.js +95 -74
  6. package/dist/base/applications/base.js.map +1 -1
  7. package/dist/base/applications/boot-steps.d.ts +13 -0
  8. package/dist/base/applications/boot-steps.d.ts.map +1 -0
  9. package/dist/base/applications/boot-steps.js +23 -0
  10. package/dist/base/applications/boot-steps.js.map +1 -0
  11. package/dist/base/applications/common/index.d.ts +2 -0
  12. package/dist/base/applications/common/index.d.ts.map +1 -0
  13. package/dist/base/applications/common/index.js +18 -0
  14. package/dist/base/applications/common/index.js.map +1 -0
  15. package/dist/base/applications/{types.d.ts → common/types.d.ts} +11 -0
  16. package/dist/base/applications/common/types.d.ts.map +1 -0
  17. package/dist/base/applications/common/types.js.map +1 -0
  18. package/dist/base/applications/index.d.ts +2 -1
  19. package/dist/base/applications/index.d.ts.map +1 -1
  20. package/dist/base/applications/index.js +2 -1
  21. package/dist/base/applications/index.js.map +1 -1
  22. package/dist/base/applications/server.d.ts +6 -2
  23. package/dist/base/applications/server.d.ts.map +1 -1
  24. package/dist/base/applications/server.js +34 -2
  25. package/dist/base/applications/server.js.map +1 -1
  26. package/dist/base/controllers/grpc/adapter.js +2 -2
  27. package/dist/base/controllers/grpc/adapter.js.map +1 -1
  28. package/dist/base/middlewares/app-error/app-error.middleware.d.ts +3 -2
  29. package/dist/base/middlewares/app-error/app-error.middleware.d.ts.map +1 -1
  30. package/dist/base/middlewares/app-error/app-error.middleware.js +4 -3
  31. package/dist/base/middlewares/app-error/app-error.middleware.js.map +1 -1
  32. package/dist/base/middlewares/request-spy/request-spy.middleware.d.ts +2 -2
  33. package/dist/base/middlewares/request-spy/request-spy.middleware.d.ts.map +1 -1
  34. package/dist/base/middlewares/request-spy/request-spy.middleware.js +20 -6
  35. package/dist/base/middlewares/request-spy/request-spy.middleware.js.map +1 -1
  36. package/dist/common/constants.d.ts.map +1 -1
  37. package/dist/common/constants.js +2 -1
  38. package/dist/common/constants.js.map +1 -1
  39. package/dist/common/environments.d.ts +31 -10
  40. package/dist/common/environments.d.ts.map +1 -1
  41. package/dist/common/environments.js +33 -10
  42. package/dist/common/environments.js.map +1 -1
  43. package/dist/components/api-reference/component.d.ts.map +1 -1
  44. package/dist/components/api-reference/component.js +4 -3
  45. package/dist/components/api-reference/component.js.map +1 -1
  46. package/dist/components/api-reference/index.d.ts +1 -1
  47. package/dist/components/api-reference/index.d.ts.map +1 -1
  48. package/dist/components/api-reference/index.js +1 -1
  49. package/dist/components/api-reference/index.js.map +1 -1
  50. package/dist/components/api-reference/ui/factory.d.ts +14 -0
  51. package/dist/components/api-reference/ui/factory.d.ts.map +1 -0
  52. package/dist/components/api-reference/ui/factory.js +54 -0
  53. package/dist/components/api-reference/ui/factory.js.map +1 -0
  54. package/dist/components/api-reference/ui/index.d.ts +4 -0
  55. package/dist/components/api-reference/ui/index.d.ts.map +1 -0
  56. package/dist/components/api-reference/ui/index.js +20 -0
  57. package/dist/components/api-reference/ui/index.js.map +1 -0
  58. package/dist/components/api-reference/ui/scalar.d.ts +8 -0
  59. package/dist/components/api-reference/ui/scalar.d.ts.map +1 -0
  60. package/dist/components/api-reference/ui/scalar.js +28 -0
  61. package/dist/components/api-reference/ui/scalar.js.map +1 -0
  62. package/dist/components/api-reference/ui/swagger.d.ts +8 -0
  63. package/dist/components/api-reference/ui/swagger.d.ts.map +1 -0
  64. package/dist/components/api-reference/ui/swagger.js +28 -0
  65. package/dist/components/api-reference/ui/swagger.js.map +1 -0
  66. package/dist/components/auth/authenticate/component.d.ts +5 -0
  67. package/dist/components/auth/authenticate/component.d.ts.map +1 -1
  68. package/dist/components/auth/authenticate/component.js +23 -0
  69. package/dist/components/auth/authenticate/component.js.map +1 -1
  70. package/dist/components/auth/authenticate/controllers/factory.d.ts +35 -3
  71. package/dist/components/auth/authenticate/controllers/factory.d.ts.map +1 -1
  72. package/dist/components/auth/authenticate/controllers/jwks/definitions.d.ts +40 -20
  73. package/dist/components/auth/authenticate/controllers/jwks/definitions.d.ts.map +1 -1
  74. package/dist/components/auth/authenticate/controllers/service-certs/definitions.d.ts +40 -20
  75. package/dist/components/auth/authenticate/controllers/service-certs/definitions.d.ts.map +1 -1
  76. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.d.ts +2 -2
  77. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.d.ts.map +1 -1
  78. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js +12 -8
  79. package/dist/components/auth/authenticate/services/bearer/jwks/issuer.service.js.map +1 -1
  80. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.d.ts +1 -1
  81. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.d.ts.map +1 -1
  82. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.js +5 -3
  83. package/dist/components/auth/authenticate/services/bearer/jwks/verifier.service.js.map +1 -1
  84. package/dist/components/auth/authenticate/services/bearer/jws.service.d.ts +1 -2
  85. package/dist/components/auth/authenticate/services/bearer/jws.service.d.ts.map +1 -1
  86. package/dist/components/auth/authenticate/services/bearer/jws.service.js +5 -3
  87. package/dist/components/auth/authenticate/services/bearer/jws.service.js.map +1 -1
  88. package/dist/components/auth/authenticate/services/jose-loader.d.ts +16 -0
  89. package/dist/components/auth/authenticate/services/jose-loader.d.ts.map +1 -0
  90. package/dist/components/auth/authenticate/services/jose-loader.js +25 -0
  91. package/dist/components/auth/authenticate/services/jose-loader.js.map +1 -0
  92. package/dist/components/auth/authenticate/services/service/signer.service.d.ts.map +1 -1
  93. package/dist/components/auth/authenticate/services/service/signer.service.js +8 -5
  94. package/dist/components/auth/authenticate/services/service/signer.service.js.map +1 -1
  95. package/dist/components/auth/authenticate/services/service/verifier.service.d.ts.map +1 -1
  96. package/dist/components/auth/authenticate/services/service/verifier.service.js +10 -9
  97. package/dist/components/auth/authenticate/services/service/verifier.service.js.map +1 -1
  98. package/dist/components/auth/authenticate/strategies/index.d.ts +1 -1
  99. package/dist/components/auth/authenticate/strategies/index.d.ts.map +1 -1
  100. package/dist/components/auth/authenticate/strategies/index.js +1 -1
  101. package/dist/components/auth/authenticate/strategies/index.js.map +1 -1
  102. package/dist/components/auth/authenticate/strategies/jwks/index.d.ts +3 -0
  103. package/dist/components/auth/authenticate/strategies/jwks/index.d.ts.map +1 -0
  104. package/dist/components/auth/authenticate/strategies/jwks/index.js +19 -0
  105. package/dist/components/auth/authenticate/strategies/jwks/index.js.map +1 -0
  106. package/dist/components/auth/authenticate/strategies/jwks/issuer.d.ts +13 -0
  107. package/dist/components/auth/authenticate/strategies/jwks/issuer.d.ts.map +1 -0
  108. package/dist/components/auth/authenticate/strategies/{jwks.strategy.js → jwks/issuer.js} +4 -27
  109. package/dist/components/auth/authenticate/strategies/jwks/issuer.js.map +1 -0
  110. package/dist/components/auth/authenticate/strategies/{jwks.strategy.d.ts → jwks/verifier.d.ts} +2 -10
  111. package/dist/components/auth/authenticate/strategies/jwks/verifier.d.ts.map +1 -0
  112. package/dist/components/auth/authenticate/strategies/jwks/verifier.js +45 -0
  113. package/dist/components/auth/authenticate/strategies/jwks/verifier.js.map +1 -0
  114. package/dist/components/auth/authorize/adapters/base-filtered.d.ts +1 -1
  115. package/dist/components/auth/authorize/adapters/base-filtered.d.ts.map +1 -1
  116. package/dist/components/auth/authorize/adapters/common/index.d.ts +2 -0
  117. package/dist/components/auth/authorize/adapters/common/index.d.ts.map +1 -0
  118. package/dist/components/auth/authorize/adapters/common/index.js +18 -0
  119. package/dist/components/auth/authorize/adapters/common/index.js.map +1 -0
  120. package/dist/components/auth/authorize/adapters/{types.d.ts → common/types.d.ts} +1 -1
  121. package/dist/components/auth/authorize/adapters/common/types.d.ts.map +1 -0
  122. package/dist/components/auth/authorize/adapters/common/types.js.map +1 -0
  123. package/dist/components/auth/authorize/adapters/connector.d.ts +1 -1
  124. package/dist/components/auth/authorize/adapters/connector.d.ts.map +1 -1
  125. package/dist/components/auth/authorize/adapters/custom-grant-expander.d.ts +1 -1
  126. package/dist/components/auth/authorize/adapters/custom-grant-expander.d.ts.map +1 -1
  127. package/dist/components/auth/authorize/adapters/index.d.ts +2 -2
  128. package/dist/components/auth/authorize/adapters/index.d.ts.map +1 -1
  129. package/dist/components/auth/authorize/adapters/index.js +2 -2
  130. package/dist/components/auth/authorize/adapters/index.js.map +1 -1
  131. package/dist/components/auth/authorize/adapters/{scoped-casbin.adapter.d.ts → scoped-casbin/adapter.d.ts} +11 -55
  132. package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.d.ts.map +1 -0
  133. package/dist/components/auth/authorize/adapters/{scoped-casbin.adapter.js → scoped-casbin/adapter.js} +27 -22
  134. package/dist/components/auth/authorize/adapters/scoped-casbin/adapter.js.map +1 -0
  135. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.d.ts +2 -0
  136. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.d.ts.map +1 -0
  137. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.js +5 -0
  138. package/dist/components/auth/authorize/adapters/scoped-casbin/common/constants.js.map +1 -0
  139. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.d.ts +3 -0
  140. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.d.ts.map +1 -0
  141. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.js +19 -0
  142. package/dist/components/auth/authorize/adapters/scoped-casbin/common/index.js.map +1 -0
  143. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.d.ts +50 -0
  144. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.d.ts.map +1 -0
  145. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.js +3 -0
  146. package/dist/components/auth/authorize/adapters/scoped-casbin/common/types.js.map +1 -0
  147. package/dist/components/auth/authorize/adapters/scoped-casbin/index.d.ts +4 -0
  148. package/dist/components/auth/authorize/adapters/scoped-casbin/index.d.ts.map +1 -0
  149. package/dist/components/auth/authorize/adapters/scoped-casbin/index.js +19 -0
  150. package/dist/components/auth/authorize/adapters/scoped-casbin/index.js.map +1 -0
  151. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.d.ts +17 -0
  152. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.d.ts.map +1 -0
  153. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.js +11 -0
  154. package/dist/components/auth/authorize/adapters/scoped-casbin/policy-edges.js.map +1 -0
  155. package/dist/components/auth/authorize/enforcers/policy-line-codec.d.ts.map +1 -1
  156. package/dist/components/auth/authorize/enforcers/policy-line-codec.js +4 -2
  157. package/dist/components/auth/authorize/enforcers/policy-line-codec.js.map +1 -1
  158. package/dist/components/auth/authorize/role-managers/base.d.ts.map +1 -1
  159. package/dist/components/auth/authorize/role-managers/base.js +2 -1
  160. package/dist/components/auth/authorize/role-managers/base.js.map +1 -1
  161. package/dist/components/auth/models/entities/policy-definition.model.d.ts +30 -9
  162. package/dist/components/auth/models/entities/policy-definition.model.d.ts.map +1 -1
  163. package/dist/components/auth/models/entities/policy-definition.model.js +36 -10
  164. package/dist/components/auth/models/entities/policy-definition.model.js.map +1 -1
  165. package/dist/components/auth/models/entities/user.model.d.ts +2 -2
  166. package/dist/components/controller/grpc/grpc.component.d.ts.map +1 -1
  167. package/dist/components/controller/grpc/grpc.component.js +1 -5
  168. package/dist/components/controller/grpc/grpc.component.js.map +1 -1
  169. package/dist/components/health-check/common/constants.d.ts +5 -0
  170. package/dist/components/health-check/common/constants.d.ts.map +1 -0
  171. package/dist/components/health-check/common/constants.js +9 -0
  172. package/dist/components/health-check/common/constants.js.map +1 -0
  173. package/dist/components/health-check/common/index.d.ts +1 -0
  174. package/dist/components/health-check/common/index.d.ts.map +1 -1
  175. package/dist/components/health-check/common/index.js +1 -0
  176. package/dist/components/health-check/common/index.js.map +1 -1
  177. package/dist/components/health-check/common/keys.d.ts +2 -0
  178. package/dist/components/health-check/common/keys.d.ts.map +1 -1
  179. package/dist/components/health-check/common/keys.js +2 -0
  180. package/dist/components/health-check/common/keys.js.map +1 -1
  181. package/dist/components/health-check/common/rest-paths.d.ts +1 -0
  182. package/dist/components/health-check/common/rest-paths.d.ts.map +1 -1
  183. package/dist/components/health-check/common/rest-paths.js +1 -0
  184. package/dist/components/health-check/common/rest-paths.js.map +1 -1
  185. package/dist/components/health-check/common/types.d.ts +22 -0
  186. package/dist/components/health-check/common/types.d.ts.map +1 -1
  187. package/dist/components/health-check/component.d.ts +9 -3
  188. package/dist/components/health-check/component.d.ts.map +1 -1
  189. package/dist/components/health-check/component.js +38 -1
  190. package/dist/components/health-check/component.js.map +1 -1
  191. package/dist/components/health-check/controller.d.ts +10 -2
  192. package/dist/components/health-check/controller.d.ts.map +1 -1
  193. package/dist/components/health-check/controller.js +79 -13
  194. package/dist/components/health-check/controller.js.map +1 -1
  195. package/dist/components/health-check/index.d.ts +1 -0
  196. package/dist/components/health-check/index.d.ts.map +1 -1
  197. package/dist/components/health-check/index.js +1 -0
  198. package/dist/components/health-check/index.js.map +1 -1
  199. package/dist/components/health-check/reporter.d.ts +98 -0
  200. package/dist/components/health-check/reporter.d.ts.map +1 -0
  201. package/dist/components/health-check/reporter.js +138 -0
  202. package/dist/components/health-check/reporter.js.map +1 -0
  203. package/dist/components/mail/common/constants.d.ts +13 -0
  204. package/dist/components/mail/common/constants.d.ts.map +1 -1
  205. package/dist/components/mail/common/constants.js +20 -1
  206. package/dist/components/mail/common/constants.js.map +1 -1
  207. package/dist/components/mail/common/errors.d.ts +25 -0
  208. package/dist/components/mail/common/errors.d.ts.map +1 -1
  209. package/dist/components/mail/common/errors.js +22 -0
  210. package/dist/components/mail/common/errors.js.map +1 -1
  211. package/dist/components/mail/common/types/index.d.ts +6 -0
  212. package/dist/components/mail/common/types/index.d.ts.map +1 -0
  213. package/dist/components/mail/common/types/index.js +22 -0
  214. package/dist/components/mail/common/types/index.js.map +1 -0
  215. package/dist/components/mail/common/types/message.d.ts +50 -0
  216. package/dist/components/mail/common/types/message.d.ts.map +1 -0
  217. package/dist/components/mail/common/types/message.js +3 -0
  218. package/dist/components/mail/common/types/message.js.map +1 -0
  219. package/dist/components/mail/common/types/options.d.ts +72 -0
  220. package/dist/components/mail/common/types/options.d.ts.map +1 -0
  221. package/dist/components/mail/common/types/options.js +3 -0
  222. package/dist/components/mail/common/types/options.js.map +1 -0
  223. package/dist/components/mail/common/types/queue.d.ts +56 -0
  224. package/dist/components/mail/common/types/queue.d.ts.map +1 -0
  225. package/dist/components/mail/common/types/queue.js +3 -0
  226. package/dist/components/mail/common/types/queue.js.map +1 -0
  227. package/dist/components/mail/common/types/template.d.ts +33 -0
  228. package/dist/components/mail/common/types/template.d.ts.map +1 -0
  229. package/dist/components/mail/common/types/template.js +3 -0
  230. package/dist/components/mail/common/types/template.js.map +1 -0
  231. package/dist/components/mail/common/types/verification.d.ts +26 -0
  232. package/dist/components/mail/common/types/verification.d.ts.map +1 -0
  233. package/dist/components/mail/common/types/verification.js +3 -0
  234. package/dist/components/mail/common/types/verification.js.map +1 -0
  235. package/dist/components/mail/component.d.ts +6 -1
  236. package/dist/components/mail/component.d.ts.map +1 -1
  237. package/dist/components/mail/component.js +68 -16
  238. package/dist/components/mail/component.js.map +1 -1
  239. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.d.ts +1 -0
  240. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.d.ts.map +1 -1
  241. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js +3 -0
  242. package/dist/components/mail/helpers/executors/bull-mq-executor.helper.js.map +1 -1
  243. package/dist/components/mail/helpers/executors/internal-queue-executor.helper.d.ts.map +1 -1
  244. package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js +2 -1
  245. package/dist/components/mail/helpers/executors/internal-queue-executor.helper.js.map +1 -1
  246. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts +19 -0
  247. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.d.ts.map +1 -0
  248. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.js +114 -0
  249. package/dist/components/mail/helpers/transporters/amazon-ses-transporter.helper.js.map +1 -0
  250. package/dist/components/mail/helpers/transporters/index.d.ts +2 -1
  251. package/dist/components/mail/helpers/transporters/index.d.ts.map +1 -1
  252. package/dist/components/mail/helpers/transporters/index.js +2 -1
  253. package/dist/components/mail/helpers/transporters/index.js.map +1 -1
  254. package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.d.ts.map +1 -1
  255. package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js +10 -9
  256. package/dist/components/mail/helpers/transporters/mailgun-transporter.helper.js.map +1 -1
  257. package/dist/components/mail/helpers/transporters/nodemail-transporter.helper.d.ts.map +1 -1
  258. package/dist/components/mail/helpers/transporters/nodemail-transporter.helper.js +12 -1
  259. package/dist/components/mail/helpers/transporters/nodemail-transporter.helper.js.map +1 -1
  260. package/dist/components/mail/providers/mail-transporter.provider.d.ts +2 -0
  261. package/dist/components/mail/providers/mail-transporter.provider.d.ts.map +1 -1
  262. package/dist/components/mail/providers/mail-transporter.provider.js +35 -17
  263. package/dist/components/mail/providers/mail-transporter.provider.js.map +1 -1
  264. package/dist/components/mail/services/generators/index.d.ts +4 -0
  265. package/dist/components/mail/services/generators/index.d.ts.map +1 -0
  266. package/dist/components/mail/services/generators/index.js +20 -0
  267. package/dist/components/mail/services/generators/index.js.map +1 -0
  268. package/dist/components/mail/services/generators/numeric-code.d.ts +7 -0
  269. package/dist/components/mail/services/generators/numeric-code.d.ts.map +1 -0
  270. package/dist/components/mail/services/generators/numeric-code.js +20 -0
  271. package/dist/components/mail/services/generators/numeric-code.js.map +1 -0
  272. package/dist/components/mail/services/generators/random-token.d.ts +7 -0
  273. package/dist/components/mail/services/generators/random-token.d.ts.map +1 -0
  274. package/dist/components/mail/services/generators/random-token.js +18 -0
  275. package/dist/components/mail/services/generators/random-token.js.map +1 -0
  276. package/dist/components/mail/services/{generator.service.d.ts → generators/verification-data.d.ts} +2 -10
  277. package/dist/components/mail/services/generators/verification-data.d.ts.map +1 -0
  278. package/dist/components/mail/services/{generator.service.js → generators/verification-data.js} +5 -29
  279. package/dist/components/mail/services/generators/verification-data.js.map +1 -0
  280. package/dist/components/mail/services/index.d.ts +1 -1
  281. package/dist/components/mail/services/index.d.ts.map +1 -1
  282. package/dist/components/mail/services/index.js +1 -1
  283. package/dist/components/mail/services/index.js.map +1 -1
  284. package/dist/components/mail/services/mail.service.d.ts.map +1 -1
  285. package/dist/components/mail/services/mail.service.js +37 -20
  286. package/dist/components/mail/services/mail.service.js.map +1 -1
  287. package/dist/components/mail/utilities/address.utility.d.ts +9 -0
  288. package/dist/components/mail/utilities/address.utility.d.ts.map +1 -0
  289. package/dist/components/mail/utilities/address.utility.js +36 -0
  290. package/dist/components/mail/utilities/address.utility.js.map +1 -0
  291. package/dist/components/mail/utilities/attachment.utility.d.ts +31 -0
  292. package/dist/components/mail/utilities/attachment.utility.d.ts.map +1 -0
  293. package/dist/components/mail/utilities/attachment.utility.js +214 -0
  294. package/dist/components/mail/utilities/attachment.utility.js.map +1 -0
  295. package/dist/components/mail/utilities/body.utility.d.ts +6 -0
  296. package/dist/components/mail/utilities/body.utility.d.ts.map +1 -0
  297. package/dist/components/mail/utilities/body.utility.js +20 -0
  298. package/dist/components/mail/utilities/body.utility.js.map +1 -0
  299. package/dist/components/mail/utilities/index.d.ts +2 -0
  300. package/dist/components/mail/utilities/index.d.ts.map +1 -1
  301. package/dist/components/mail/utilities/index.js +2 -0
  302. package/dist/components/mail/utilities/index.js.map +1 -1
  303. package/dist/components/mail/utilities/mime.utility.d.ts +19 -0
  304. package/dist/components/mail/utilities/mime.utility.d.ts.map +1 -0
  305. package/dist/components/mail/utilities/mime.utility.js +390 -0
  306. package/dist/components/mail/utilities/mime.utility.js.map +1 -0
  307. package/dist/components/static-asset/common/commit-token.d.ts +23 -0
  308. package/dist/components/static-asset/common/commit-token.d.ts.map +1 -0
  309. package/dist/components/static-asset/common/commit-token.js +40 -0
  310. package/dist/components/static-asset/common/commit-token.js.map +1 -0
  311. package/dist/components/static-asset/common/constants.d.ts +21 -1
  312. package/dist/components/static-asset/common/constants.d.ts.map +1 -1
  313. package/dist/components/static-asset/common/constants.js +39 -2
  314. package/dist/components/static-asset/common/constants.js.map +1 -1
  315. package/dist/components/static-asset/common/errors.d.ts +52 -0
  316. package/dist/components/static-asset/common/errors.d.ts.map +1 -1
  317. package/dist/components/static-asset/common/errors.js +46 -0
  318. package/dist/components/static-asset/common/errors.js.map +1 -1
  319. package/dist/components/static-asset/common/index.d.ts +1 -0
  320. package/dist/components/static-asset/common/index.d.ts.map +1 -1
  321. package/dist/components/static-asset/common/index.js +1 -0
  322. package/dist/components/static-asset/common/index.js.map +1 -1
  323. package/dist/components/static-asset/common/keys.d.ts +1 -0
  324. package/dist/components/static-asset/common/keys.d.ts.map +1 -1
  325. package/dist/components/static-asset/common/keys.js +1 -0
  326. package/dist/components/static-asset/common/keys.js.map +1 -1
  327. package/dist/components/static-asset/common/types.d.ts +100 -30
  328. package/dist/components/static-asset/common/types.d.ts.map +1 -1
  329. package/dist/components/static-asset/component.d.ts +4 -1
  330. package/dist/components/static-asset/component.d.ts.map +1 -1
  331. package/dist/components/static-asset/component.js +21 -10
  332. package/dist/components/static-asset/component.js.map +1 -1
  333. package/dist/components/static-asset/controller/base.definition.d.ts +27 -554
  334. package/dist/components/static-asset/controller/base.definition.d.ts.map +1 -1
  335. package/dist/components/static-asset/controller/base.definition.js +259 -286
  336. package/dist/components/static-asset/controller/base.definition.js.map +1 -1
  337. package/dist/components/static-asset/controller/factory.d.ts +76 -7
  338. package/dist/components/static-asset/controller/factory.d.ts.map +1 -1
  339. package/dist/components/static-asset/controller/factory.js +810 -328
  340. package/dist/components/static-asset/controller/factory.js.map +1 -1
  341. package/dist/components/static-asset/controller/key-scope.d.ts +28 -0
  342. package/dist/components/static-asset/controller/key-scope.d.ts.map +1 -0
  343. package/dist/components/static-asset/controller/key-scope.js +44 -0
  344. package/dist/components/static-asset/controller/key-scope.js.map +1 -0
  345. package/dist/components/static-asset/controller/meta-link.d.ts +37 -0
  346. package/dist/components/static-asset/controller/meta-link.d.ts.map +1 -0
  347. package/dist/components/static-asset/controller/meta-link.js +95 -0
  348. package/dist/components/static-asset/controller/meta-link.js.map +1 -0
  349. package/dist/components/static-asset/controller/route-options.d.ts +18 -0
  350. package/dist/components/static-asset/controller/route-options.d.ts.map +1 -0
  351. package/dist/components/static-asset/controller/route-options.js +37 -0
  352. package/dist/components/static-asset/controller/route-options.js.map +1 -0
  353. package/dist/components/static-asset/index.d.ts +1 -0
  354. package/dist/components/static-asset/index.d.ts.map +1 -1
  355. package/dist/components/static-asset/index.js +1 -0
  356. package/dist/components/static-asset/index.js.map +1 -1
  357. package/dist/components/static-asset/ingest.d.ts +58 -0
  358. package/dist/components/static-asset/ingest.d.ts.map +1 -0
  359. package/dist/components/static-asset/ingest.js +82 -0
  360. package/dist/components/static-asset/ingest.js.map +1 -0
  361. package/dist/components/static-asset/models/base.model.d.ts +44 -6
  362. package/dist/components/static-asset/models/base.model.d.ts.map +1 -1
  363. package/dist/components/static-asset/models/base.model.js +7 -1
  364. package/dist/components/static-asset/models/base.model.js.map +1 -1
  365. package/dist/components/websocket/handlers/bun.handler.d.ts.map +1 -1
  366. package/dist/components/websocket/handlers/bun.handler.js +2 -1
  367. package/dist/components/websocket/handlers/bun.handler.js.map +1 -1
  368. package/dist/connectors/postgres/drivers/postgres-js.d.mts +3 -0
  369. package/dist/connectors/postgres/drivers/postgres-js.d.mts.map +1 -0
  370. package/dist/connectors/postgres/drivers/postgres-js.mjs +3 -0
  371. package/dist/connectors/postgres/drivers/postgres-js.mjs.map +1 -0
  372. package/dist/helpers/index.d.ts.map +1 -1
  373. package/dist/helpers/index.js.map +1 -1
  374. package/dist/index.d.ts +1 -1
  375. package/dist/index.d.ts.map +1 -1
  376. package/dist/testing/index.d.ts +3 -0
  377. package/dist/testing/index.d.ts.map +1 -0
  378. package/dist/testing/index.js +19 -0
  379. package/dist/testing/index.js.map +1 -0
  380. package/package.json +33 -26
  381. package/dist/base/applications/types.d.ts.map +0 -1
  382. package/dist/base/applications/types.js.map +0 -1
  383. package/dist/components/api-reference/ui-factory.d.ts +0 -24
  384. package/dist/components/api-reference/ui-factory.d.ts.map +0 -1
  385. package/dist/components/api-reference/ui-factory.js +0 -97
  386. package/dist/components/api-reference/ui-factory.js.map +0 -1
  387. package/dist/components/auth/authenticate/strategies/jwks.strategy.d.ts.map +0 -1
  388. package/dist/components/auth/authenticate/strategies/jwks.strategy.js.map +0 -1
  389. package/dist/components/auth/authorize/adapters/scoped-casbin.adapter.d.ts.map +0 -1
  390. package/dist/components/auth/authorize/adapters/scoped-casbin.adapter.js.map +0 -1
  391. package/dist/components/auth/authorize/adapters/types.d.ts.map +0 -1
  392. package/dist/components/auth/authorize/adapters/types.js.map +0 -1
  393. package/dist/components/mail/common/types.d.ts +0 -201
  394. package/dist/components/mail/common/types.d.ts.map +0 -1
  395. package/dist/components/mail/common/types.js +0 -10
  396. package/dist/components/mail/common/types.js.map +0 -1
  397. package/dist/components/mail/services/generator.service.d.ts.map +0 -1
  398. package/dist/components/mail/services/generator.service.js.map +0 -1
  399. /package/dist/base/applications/{types.js → common/types.js} +0 -0
  400. /package/dist/components/auth/authorize/adapters/{types.js → common/types.js} +0 -0
@@ -9,22 +9,136 @@ var __metadata = (this && this.__metadata) || function (k, v) {
9
9
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.AssetControllerFactory = void 0;
12
+ exports.AssetControllerFactory = exports.resolveServedContentType = exports.buildObjectLink = exports.findLabelsInQuery = void 0;
13
13
  const controllers_1 = require("../../../base/controllers");
14
14
  const metadata_1 = require("../../../base/metadata");
15
- const core_1 = require("@venizia/ignis-helpers/core");
16
- const common_1 = require("@venizia/ignis-helpers/common");
17
15
  const ignis_helpers_1 = require("@venizia/ignis-helpers");
16
+ const common_1 = require("@venizia/ignis-helpers/common");
17
+ const core_1 = require("@venizia/ignis-helpers/core");
18
18
  const node_fs_1 = require("node:fs");
19
19
  const common_2 = require("../common");
20
+ const common_3 = require("../common");
20
21
  const base_definition_1 = require("./base.definition");
22
+ const key_scope_1 = require("./key-scope");
23
+ const meta_link_1 = require("./meta-link");
24
+ const route_options_1 = require("./route-options");
21
25
  /** Hono ALREADY percent-decodes path params - a second decodeURIComponent throws on `report_100%.pdf` and turns `a%2Fb.png` into a DIFFERENT object; `isValidName`/`isValidPath` still run on this value, so traversal is still rejected. */
26
+ /** The upload labels, read off the multipart form. Every value arrives as text, so `sequence` is the one that needs converting. */
27
+ /** One list: the reader below and the stale-caller warning must agree. */
28
+ const UPLOAD_LABEL_NAMES = [
29
+ 'principalType',
30
+ 'principalId',
31
+ 'variant',
32
+ 'sequence',
33
+ 'folderPath',
34
+ ];
35
+ /** Names any label a caller put in the query - where the upload routes refuse it. */
36
+ const findLabelsInQuery = (opts) => (opts.names ?? UPLOAD_LABEL_NAMES).filter(name => opts.query[name] !== undefined);
37
+ exports.findLabelsInQuery = findLabelsInQuery;
38
+ /** Refused before the body is read: a route that ignored them would store an unlabelled row. */
39
+ const refuseLabelsInQuery = (opts) => {
40
+ const names = (0, exports.findLabelsInQuery)(opts);
41
+ if (names.length > 0) {
42
+ throw (0, core_1.getError)({
43
+ error: common_2.StaticAssetErrors.LABELS_IN_QUERY,
44
+ message: `${opts.reason ?? 'Upload labels belong in the request body, not the query'} | names: ${names.join(', ')}`,
45
+ });
46
+ }
47
+ };
48
+ /** The commit body carries four of the five: the object key - and so `folderPath` - was fixed when the policy was issued. */
49
+ const COMMIT_LABEL_NAMES = UPLOAD_LABEL_NAMES.filter(name => name !== 'folderPath');
50
+ // A multipart form hands strings; a JSON commit body may hand `sequence` as a number.
51
+ const readUploadLabels = (opts) => {
52
+ const { fields } = opts;
53
+ const read = (name) => {
54
+ const value = fields[name];
55
+ return value === undefined || value === '' ? undefined : String(value);
56
+ };
57
+ const sequence = read('sequence');
58
+ return {
59
+ principalType: read('principalType'),
60
+ principalId: read('principalId'),
61
+ variant: read('variant'),
62
+ sequence: sequence === undefined ? undefined : Number(sequence),
63
+ folderPath: read('folderPath'),
64
+ };
65
+ };
22
66
  const readObjectName = rawObjectName => rawObjectName;
23
67
  /** Encodes an object path into a SINGLE url segment: `{objectName}` matches one segment only, so `/` must be percent-encoded too (Hono decodes it back before the handler reads the param). */
24
68
  const encodeObjectPath = objectPath => {
25
69
  return encodeURIComponent(objectPath);
26
70
  };
27
- /** Sets whitelisted metadata headers on the response context. */
71
+ /** The bucket one request works on: a configured value wins - called on every request, so an application may read an environment variable lazily - else the path param, which is only registered when there is no configured bucket. */
72
+ const resolveBucket = async (opts) => {
73
+ const { configured, param } = opts;
74
+ if (configured === undefined) {
75
+ return param ?? '';
76
+ }
77
+ return (0, common_1.resolveValueAsync)({ value: configured });
78
+ };
79
+ /** The URL of one object, in the shape its controller serves: no `/buckets/<name>` with a configured bucket, and a raw path with `rawObjectPath`. */
80
+ const buildObjectLink = (opts) => {
81
+ const { basePath, bucket, object } = opts;
82
+ const { hasConfiguredBucket = false, rawObjectPath = false } = opts;
83
+ const bucketSegment = hasConfiguredBucket ? '' : `/buckets/${bucket.name}`;
84
+ const objectSegment = rawObjectPath ? object.key : encodeObjectPath(object.key);
85
+ return `${basePath}${bucketSegment}/objects/${objectSegment}`;
86
+ };
87
+ exports.buildObjectLink = buildObjectLink;
88
+ /** Mirrors `BaseStorageHelper.normalizeObjectName`, which is protected: `resolveObjectName` is offered the very name the helper would otherwise have written. */
89
+ const defaultObjectName = ({ file }) => {
90
+ const { originalName, folderPath } = file;
91
+ const normalizedFileName = originalName.toLowerCase().replace(/ /g, '_');
92
+ if (!folderPath) {
93
+ return normalizedFileName;
94
+ }
95
+ return `${folderPath.toLowerCase().replace(/ /g, '_')}/${normalizedFileName}`;
96
+ };
97
+ /**
98
+ * The served type comes from the object NAME, never the backend or the uploader: a renderable type on
99
+ * the API origin is stored XSS. Exported so a hand-written route makes the same decision - the
100
+ * backend's own content-type is dropped by `WHITELIST_HEADERS`.
101
+ */
102
+ const resolveServedContentType = (opts) => {
103
+ const { helper, object } = opts;
104
+ const candidate = helper.getMimeType({ filename: object.key }).toLowerCase().split(';')[0].trim();
105
+ if (!common_2.RENDERABLE_CONTENT_TYPES.has(candidate)) {
106
+ return { contentType: common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM, isRenderable: false };
107
+ }
108
+ return { contentType: candidate, isRenderable: true };
109
+ };
110
+ exports.resolveServedContentType = resolveServedContentType;
111
+ /**
112
+ * Reads one `Range` header. Only a single byte range is honoured - multipart ranges need a multipart
113
+ * body no browser asks for here. An unsatisfiable or malformed range returns `null`, and the caller
114
+ * serves the whole object, which is what RFC 9110 allows.
115
+ */
116
+ const readByteRange = (opts) => {
117
+ const { header, size } = opts;
118
+ if (!header || size <= 0) {
119
+ return null;
120
+ }
121
+ const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
122
+ if (!match) {
123
+ return null;
124
+ }
125
+ const [, rawStart, rawEnd] = match;
126
+ // `bytes=-500` means the LAST 500 bytes, not "from 0 to 500".
127
+ if (!rawStart) {
128
+ const suffixLength = Number(rawEnd);
129
+ if (!Number.isInteger(suffixLength) || suffixLength <= 0) {
130
+ return null;
131
+ }
132
+ return { start: Math.max(size - suffixLength, 0), end: size - 1 };
133
+ }
134
+ const start = Number(rawStart);
135
+ const end = rawEnd ? Number(rawEnd) : size - 1;
136
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start > end || start >= size) {
137
+ return null;
138
+ }
139
+ return { start, end: Math.min(end, size - 1) };
140
+ };
141
+ /** Sets whitelisted metadata headers on the response context. `content-type` is not among them. */
28
142
  const applyMetadataHeaders = ({ ctx, metadata }) => {
29
143
  Object.entries(metadata).forEach(([key, value]) => {
30
144
  if (!common_2.WHITELIST_HEADERS.includes(key.toLowerCase())) {
@@ -39,10 +153,40 @@ class AssetControllerFactory extends core_1.BaseHelper {
39
153
  }
40
154
  static defineAssetController(opts) {
41
155
  const { controller, helper, options, useMetaLink, metaLink, storage } = opts;
42
- const { name, basePath, routes, isStrict = true } = controller;
156
+ const { resolveObjectName, defineRoutesBefore, defineExtraRoutes } = opts;
157
+ const { directUpload } = controller;
158
+ const { name, basePath, routes, isStrict = true, bucket, rawObjectPath = false } = controller;
43
159
  const maxFolderDepth = options?.maxFolderDepth ?? ignis_helpers_1.BaseStorageHelper.DEFAULT_MAX_FOLDER_DEPTH;
160
+ const maxBytes = options?.maxBytes;
161
+ const hasConfiguredBucket = bucket !== undefined;
162
+ // With the bucket in the URL, the bucket routes stay unscoped and the scope spans every bucket.
163
+ if (controller.keyPrefix !== undefined && !hasConfiguredBucket) {
164
+ throw (0, core_1.getError)({
165
+ message: `[${name}] keyPrefix needs controller.bucket | a bucket in the URL is a bucket the caller chooses, and the bucket routes cannot be scoped`,
166
+ });
167
+ }
168
+ const scope = controller.keyPrefix === undefined
169
+ ? undefined
170
+ : (0, key_scope_1.normalizeKeyPrefix)({ keyPrefix: controller.keyPrefix, helper, scope: name });
171
+ const keyPrefix = scope?.keyPrefix;
172
+ // `maxFolderDepth` counts the caller's folders; the scope's own are configuration, not input.
173
+ const objectKeyMaxDepth = maxFolderDepth + (scope?.depth ?? 0);
174
+ // The component's own name under a scope: the original name must stay one segment, as without one.
175
+ const isScopedDefaultNaming = keyPrefix !== undefined && !resolveObjectName && !options?.normalizeNameFn;
176
+ const definitions = (0, base_definition_1.buildAssetDefinitions)({ hasConfiguredBucket, rawObjectPath });
44
177
  // The mount path may be declared with or without a leading slash; a link must carry exactly one.
45
178
  const normalizedBasePath = basePath.startsWith('/') ? basePath : `/${basePath}`;
179
+ /** Only when the URL shape deviates: with neither option the storage helper's own link is left untouched. */
180
+ const shapedLinkFn = hasConfiguredBucket || rawObjectPath
181
+ ? (linkOptions) => (0, exports.buildObjectLink)({
182
+ basePath: normalizedBasePath,
183
+ bucket: linkOptions.bucket,
184
+ object: linkOptions.object,
185
+ hasConfiguredBucket,
186
+ rawObjectPath,
187
+ })
188
+ : undefined;
189
+ const normalizeLinkFn = options?.normalizeLinkFn ?? shapedLinkFn;
46
190
  let GeneratedStaticAssetController = class GeneratedStaticAssetController extends controllers_1.BaseRestController {
47
191
  constructor() {
48
192
  super({
@@ -65,362 +209,700 @@ class AssetControllerFactory extends core_1.BaseHelper {
65
209
  }
66
210
  }
67
211
  binding() {
68
- this.bindRoute({
69
- configs: { ...base_definition_1.StaticAssetDefinitions.GET_BUCKETS, ...routes?.getBuckets },
70
- }).to({
71
- handler: async (ctx) => {
72
- const bucket = await helper.getBuckets();
73
- return ctx.json(bucket, common_1.HTTP.ResultCodes.RS_2.Ok);
74
- },
75
- });
76
- this.bindRoute({
77
- configs: { ...base_definition_1.StaticAssetDefinitions.GET_BUCKET_BY_NAME, ...routes?.getBucketByName },
78
- }).to({
79
- handler: async (ctx) => {
80
- const { bucketName } = ctx.req.valid('param');
81
- if (!helper.isValidName(bucketName)) {
82
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
83
- }
84
- const bucket = await helper.getBucket({ name: bucketName });
85
- return ctx.json(bucket, common_1.HTTP.ResultCodes.RS_2.Ok);
86
- },
87
- });
88
- this.bindRoute({
89
- configs: { ...base_definition_1.StaticAssetDefinitions.GET_OBJECT_BY_NAME, ...routes?.getObjectByName },
90
- }).to({
91
- handler: async (ctx) => {
92
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
93
- const objectName = readObjectName(rawObjectName);
94
- if (!helper.isValidName(bucketName)) {
95
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
96
- }
97
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
98
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
99
- }
100
- const fileStat = await helper.getStat({ bucket: bucketName, name: objectName });
101
- const { size, metadata } = fileStat;
102
- applyMetadataHeaders({ ctx, metadata });
103
- if (!ctx.res.headers.has(common_1.HTTP.Headers.CONTENT_TYPE)) {
104
- ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM);
105
- }
106
- ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
107
- ctx.header('x-content-type-options', 'nosniff');
108
- const stream = await helper.getFile({ bucket: bucketName, name: objectName });
109
- return new Response(stream, {
110
- headers: ctx.res.headers,
111
- status: common_1.HTTP.ResultCodes.RS_2.Ok,
212
+ // First, so a literal path can win over the built-in catch-all `rawObjectPath` registers.
213
+ defineRoutesBefore?.({ controller: this, helper, basePath: normalizedBasePath });
214
+ // A single-bucket application has no reason to expose bucket creation or deletion, so the
215
+ // four bucket-management routes are not registered once `bucket` is configured.
216
+ if (!hasConfiguredBucket) {
217
+ const getBucketsRoute = (0, route_options_1.readRouteOverride)({ route: routes?.getBuckets });
218
+ if (getBucketsRoute.isEnabled) {
219
+ this.bindRoute({
220
+ configs: { ...definitions.GET_BUCKETS, ...getBucketsRoute.override },
221
+ }).to({
222
+ handler: async (ctx) => {
223
+ const buckets = await helper.getBuckets();
224
+ return ctx.json(buckets, common_1.HTTP.ResultCodes.RS_2.Ok);
225
+ },
112
226
  });
113
- },
114
- });
115
- this.bindRoute({
116
- configs: {
117
- ...base_definition_1.StaticAssetDefinitions.DOWNLOAD_OBJECT_BY_NAME,
118
- ...routes?.downloadObjectByName,
119
- },
120
- }).to({
121
- handler: async (ctx) => {
122
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
123
- const objectName = readObjectName(rawObjectName);
124
- if (!helper.isValidName(bucketName)) {
125
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
126
- }
127
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
128
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
129
- }
130
- const fileStat = await helper.getStat({ bucket: bucketName, name: objectName });
131
- const { size, metadata } = fileStat;
132
- applyMetadataHeaders({ ctx, metadata });
133
- if (!ctx.res.headers.has(common_1.HTTP.Headers.CONTENT_TYPE)) {
134
- ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, common_1.HTTP.HeaderValues.APPLICATION_OCTET_STREAM);
135
- }
136
- ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
137
- const fileName = objectName.split('/').pop() ?? objectName;
138
- ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: fileName, type: 'attachment' }));
139
- ctx.header('x-content-type-options', 'nosniff');
140
- const stream = await helper.getFile({ bucket: bucketName, name: objectName });
141
- return new Response(stream, {
142
- headers: ctx.res.headers,
143
- status: common_1.HTTP.ResultCodes.RS_2.Ok,
227
+ }
228
+ const getBucketByNameRoute = (0, route_options_1.readRouteOverride)({ route: routes?.getBucketByName });
229
+ if (getBucketByNameRoute.isEnabled) {
230
+ this.bindRoute({
231
+ configs: { ...definitions.GET_BUCKET_BY_NAME, ...getBucketByNameRoute.override },
232
+ }).to({
233
+ handler: async (ctx) => {
234
+ const params = ctx.req.valid('param');
235
+ const bucketName = await resolveBucket({
236
+ configured: bucket,
237
+ param: params.bucketName,
238
+ });
239
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
240
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
241
+ }
242
+ const found = await helper.getBucket({ bucket: { name: bucketName } });
243
+ return ctx.json(found, common_1.HTTP.ResultCodes.RS_2.Ok);
244
+ },
144
245
  });
145
- },
146
- });
147
- this.bindRoute({
148
- configs: { ...base_definition_1.StaticAssetDefinitions.CREATE_BUCKET, ...routes?.createBucket },
149
- }).to({
150
- handler: async (ctx) => {
151
- const { bucketName } = ctx.req.valid('param');
152
- if (!helper.isValidName(bucketName)) {
153
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
154
- }
155
- const createdBucket = await helper.createBucket({ name: bucketName });
156
- return ctx.json(createdBucket, common_1.HTTP.ResultCodes.RS_2.Ok);
157
- },
158
- });
159
- this.bindRoute({
160
- configs: { ...base_definition_1.StaticAssetDefinitions.UPLOAD, ...routes?.upload },
161
- }).to({
162
- handler: async (ctx) => {
163
- const { bucketName } = ctx.req.valid('param');
164
- const query = ctx.req.valid('query');
165
- if (!helper.isValidName(bucketName)) {
166
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
167
- }
168
- const folderPath = query.folderPath;
169
- if (folderPath) {
170
- const normalizedFolder = folderPath.replace(/^\/+|\/+$/g, '');
171
- if (!normalizedFolder) {
172
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.FOLDER_PATH_INVALID });
173
- }
174
- // maxFolderDepth excludes the filename, so it is compared directly against segment count.
175
- const folderSegments = normalizedFolder.split('/');
176
- if (folderSegments.length > maxFolderDepth) {
177
- throw (0, core_1.getError)({
178
- error: common_2.StaticAssetErrors.FOLDER_DEPTH_EXCEEDED,
179
- message: `Folder path exceeds max depth of ${maxFolderDepth}`,
246
+ }
247
+ const createBucketRoute = (0, route_options_1.readRouteOverride)({ route: routes?.createBucket });
248
+ if (createBucketRoute.isEnabled) {
249
+ this.bindRoute({
250
+ configs: { ...definitions.CREATE_BUCKET, ...createBucketRoute.override },
251
+ }).to({
252
+ handler: async (ctx) => {
253
+ const params = ctx.req.valid('param');
254
+ const bucketName = await resolveBucket({
255
+ configured: bucket,
256
+ param: params.bucketName,
180
257
  });
181
- }
182
- const invalidSegmentIndex = folderSegments.findIndex(segment => !helper.isValidName(segment));
183
- if (invalidSegmentIndex !== -1) {
184
- throw (0, core_1.getError)({
185
- error: common_2.StaticAssetErrors.FOLDER_SEGMENT_INVALID,
186
- message: `Invalid folder path segment: ${folderSegments[invalidSegmentIndex]}`,
258
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
259
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
260
+ }
261
+ const createdBucket = await helper.createBucket({ bucket: { name: bucketName } });
262
+ return ctx.json(createdBucket, common_1.HTTP.ResultCodes.RS_2.Ok);
263
+ },
264
+ });
265
+ }
266
+ const deleteBucketRoute = (0, route_options_1.readRouteOverride)({ route: routes?.deleteBucket });
267
+ if (deleteBucketRoute.isEnabled) {
268
+ this.bindRoute({
269
+ configs: { ...definitions.DELETE_BUCKET, ...deleteBucketRoute.override },
270
+ }).to({
271
+ handler: async (ctx) => {
272
+ const params = ctx.req.valid('param');
273
+ const bucketName = await resolveBucket({
274
+ configured: bucket,
275
+ param: params.bucketName,
187
276
  });
277
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
278
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
279
+ }
280
+ const isRemovedBucket = await helper.removeBucket({
281
+ bucket: { name: bucketName },
282
+ });
283
+ return ctx.json({ isDeleted: isRemovedBucket }, common_1.HTTP.ResultCodes.RS_2.Ok);
284
+ },
285
+ });
286
+ }
287
+ }
288
+ const getObjectByNameRoute = (0, route_options_1.readRouteOverride)({ route: routes?.getObjectByName });
289
+ if (getObjectByNameRoute.isEnabled) {
290
+ this.bindRoute({
291
+ configs: { ...definitions.GET_OBJECT_BY_NAME, ...getObjectByNameRoute.override },
292
+ }).to({
293
+ handler: async (ctx) => {
294
+ const params = ctx.req.valid('param');
295
+ const bucketName = await resolveBucket({
296
+ configured: bucket,
297
+ param: params.bucketName,
298
+ });
299
+ const objectName = readObjectName(params.objectName);
300
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
301
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
302
+ }
303
+ if (!helper.isValidObjectKey({
304
+ object: { key: objectName },
305
+ maxDepth: objectKeyMaxDepth,
306
+ })) {
307
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
308
+ }
309
+ // Before any storage call: a key outside the scope must not even be probed.
310
+ (0, key_scope_1.assertKeyInScope)({ key: objectName, keyPrefix });
311
+ const fileStat = await helper.getStat({
312
+ bucket: { name: bucketName },
313
+ object: { key: objectName },
314
+ });
315
+ const { size, metadata } = fileStat;
316
+ applyMetadataHeaders({ ctx, metadata });
317
+ const served = (0, exports.resolveServedContentType)({ helper, object: { key: objectName } });
318
+ ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, served.contentType);
319
+ ctx.header('x-content-type-options', 'nosniff');
320
+ // Sandboxed even when renderable, so an unforeseen renderable type cannot reach this origin.
321
+ ctx.header(common_1.HTTP.Headers.CONTENT_SECURITY_POLICY, 'sandbox');
322
+ // Advertised unconditionally: a player only offers seeking once it sees this.
323
+ ctx.header('accept-ranges', 'bytes');
324
+ // A type outside the allow-list downloads instead of rendering; that is the XSS boundary.
325
+ if (!served.isRenderable) {
326
+ const inlineName = objectName.split('/').pop() ?? objectName;
327
+ ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: inlineName, type: 'attachment' }));
328
+ }
329
+ const range = readByteRange({ header: ctx.req.header('range'), size });
330
+ if (range) {
331
+ ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, String(range.end - range.start + 1));
332
+ ctx.header(common_1.HTTP.Headers.CONTENT_RANGE, `bytes ${range.start}-${range.end}/${size}`);
333
+ }
334
+ else {
335
+ ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
336
+ }
337
+ // The web stream is what a Response body wants, so no Node Readable sits in between.
338
+ const stream = await helper.getObjectStream({
339
+ bucket: { name: bucketName },
340
+ object: { key: objectName },
341
+ ...(range ? { range } : {}),
342
+ });
343
+ return new Response(stream, {
344
+ headers: ctx.res.headers,
345
+ status: range ? common_1.HTTP.ResultCodes.RS_2.PartialContent : common_1.HTTP.ResultCodes.RS_2.Ok,
346
+ });
347
+ },
348
+ });
349
+ }
350
+ const downloadObjectByNameRoute = (0, route_options_1.readRouteOverride)({
351
+ route: routes?.downloadObjectByName,
352
+ });
353
+ if (downloadObjectByNameRoute.isEnabled) {
354
+ this.bindRoute({
355
+ configs: {
356
+ ...definitions.DOWNLOAD_OBJECT_BY_NAME,
357
+ ...downloadObjectByNameRoute.override,
358
+ },
359
+ }).to({
360
+ handler: async (ctx) => {
361
+ const params = ctx.req.valid('param');
362
+ const bucketName = await resolveBucket({
363
+ configured: bucket,
364
+ param: params.bucketName,
365
+ });
366
+ const objectName = readObjectName(params.objectName);
367
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
368
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
188
369
  }
189
- }
190
- const filesArray = await (0, ignis_helpers_1.parseMultipartBody)({
191
- context: ctx,
192
- storage: options?.parseMultipartBody?.storage,
193
- uploadDir: options?.parseMultipartBody?.uploadDir,
370
+ if (!helper.isValidObjectKey({
371
+ object: { key: objectName },
372
+ maxDepth: objectKeyMaxDepth,
373
+ })) {
374
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
375
+ }
376
+ // Before any storage call: a key outside the scope must not even be probed.
377
+ (0, key_scope_1.assertKeyInScope)({ key: objectName, keyPrefix });
378
+ const fileStat = await helper.getStat({
379
+ bucket: { name: bucketName },
380
+ object: { key: objectName },
381
+ });
382
+ const { size, metadata } = fileStat;
383
+ applyMetadataHeaders({ ctx, metadata });
384
+ // Download always attaches, so the served type only has to be honest, never renderable.
385
+ const served = (0, exports.resolveServedContentType)({ helper, object: { key: objectName } });
386
+ ctx.header(common_1.HTTP.Headers.CONTENT_TYPE, served.contentType);
387
+ ctx.header(common_1.HTTP.Headers.CONTENT_LENGTH, size.toString());
388
+ const fileName = objectName.split('/').pop() ?? objectName;
389
+ ctx.header(common_1.HTTP.Headers.CONTENT_DISPOSITION, (0, ignis_helpers_1.createContentDispositionHeader)({ filename: fileName, type: 'attachment' }));
390
+ ctx.header('x-content-type-options', 'nosniff');
391
+ ctx.header(common_1.HTTP.Headers.CONTENT_SECURITY_POLICY, 'sandbox');
392
+ const stream = await helper.getObjectStream({
393
+ bucket: { name: bucketName },
394
+ object: { key: objectName },
395
+ });
396
+ return new Response(stream, {
397
+ headers: ctx.res.headers,
398
+ status: common_1.HTTP.ResultCodes.RS_2.Ok,
399
+ });
400
+ },
401
+ });
402
+ }
403
+ const uploadRoute = (0, route_options_1.readRouteOverride)({ route: routes?.upload });
404
+ if (uploadRoute.isEnabled) {
405
+ const uploadMiddleware = maxBytes === undefined
406
+ ? uploadRoute.override.middleware
407
+ : (0, route_options_1.appendDeclaredLengthGuard)({
408
+ middleware: uploadRoute.override.middleware,
409
+ maxBytes,
194
410
  });
195
- const spoolPaths = filesArray
196
- .map(file => file.path)
197
- .filter((filePath) => Boolean(filePath));
198
- let uploaded;
199
- try {
200
- // `storage: 'disk'` spools the payload to `uploadDir` and returns `path` instead of `buffer`; the storage helpers only ever write `buffer`, so it must be read back.
201
- const modifiedFiles = filesArray.map(file => {
202
- const buffer = file.buffer ?? (file.path ? (0, node_fs_1.readFileSync)(file.path) : undefined);
203
- if (!buffer?.length) {
411
+ this.bindRoute({
412
+ configs: {
413
+ ...definitions.UPLOAD,
414
+ ...uploadRoute.override,
415
+ middleware: uploadMiddleware,
416
+ },
417
+ }).to({
418
+ handler: async (ctx) => {
419
+ // A configured bucket drops `params` from this route entirely, so it is read only when it exists.
420
+ const bucketParam = hasConfiguredBucket
421
+ ? undefined
422
+ : ctx.req.valid('param').bucketName;
423
+ const bucketName = await resolveBucket({ configured: bucket, param: bucketParam });
424
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
425
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
426
+ }
427
+ const validateFolderPath = (folderPath) => {
428
+ const normalizedFolder = folderPath.replace(/^\/+|\/+$/g, '');
429
+ if (!normalizedFolder) {
430
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.FOLDER_PATH_INVALID });
431
+ }
432
+ // maxFolderDepth excludes the filename, so it is compared directly against segment count.
433
+ const folderSegments = normalizedFolder.split('/');
434
+ if (folderSegments.length > maxFolderDepth) {
204
435
  throw (0, core_1.getError)({
205
- error: common_2.StaticAssetErrors.FILE_EMPTY,
206
- message: `Empty file content | name: ${file.originalname}`,
436
+ error: common_2.StaticAssetErrors.FOLDER_DEPTH_EXCEEDED,
437
+ message: `Folder path exceeds max depth of ${maxFolderDepth}`,
207
438
  });
208
439
  }
209
- return {
210
- originalName: file.originalname,
211
- mimetype: file.mimetype,
212
- buffer,
213
- size: file.size,
214
- encoding: file.encoding,
215
- folderPath: folderPath ?? undefined,
216
- };
217
- });
218
- uploaded = await helper.upload({
219
- bucket: bucketName,
220
- files: modifiedFiles,
221
- normalizeNameFn: options?.normalizeNameFn,
222
- normalizeLinkFn: options?.normalizeLinkFn,
223
- // Without this the helper re-validates against its own hard default of 2, so an app configured for a deeper tree spools the body and only then fails inside the helper.
224
- maxFolderDepth,
440
+ const invalidSegmentIndex = folderSegments.findIndex(segment => !helper.isValidSegment({ segment }));
441
+ if (invalidSegmentIndex !== -1) {
442
+ throw (0, core_1.getError)({
443
+ error: common_2.StaticAssetErrors.FOLDER_SEGMENT_INVALID,
444
+ message: `Invalid folder path segment: ${folderSegments[invalidSegmentIndex]}`,
445
+ });
446
+ }
447
+ };
448
+ refuseLabelsInQuery({ query: ctx.req.query() });
449
+ const { files: filesArray, fields } = await (0, ignis_helpers_1.parseMultipartBody)({
450
+ context: ctx,
451
+ storage: options?.parseMultipartBody?.storage,
452
+ uploadDir: options?.parseMultipartBody?.uploadDir,
225
453
  });
226
- }
227
- finally {
228
- this.removeSpoolFiles({ paths: spoolPaths });
229
- }
230
- if (!useMetaLink || !metaLink) {
231
- return ctx.json(uploaded, common_1.HTTP.ResultCodes.RS_2.Ok);
232
- }
233
- const results = [];
234
- for (const uploadResult of uploaded) {
454
+ // Labels travel with the payload, not the URL, so the folder check waits for the parse.
455
+ const query = readUploadLabels({ fields });
456
+ const folderPath = query.folderPath;
457
+ if (folderPath) {
458
+ validateFolderPath(folderPath);
459
+ }
460
+ const spoolPaths = filesArray
461
+ .map(file => file.path)
462
+ .filter((filePath) => Boolean(filePath));
463
+ let uploaded;
235
464
  try {
236
- const fileStat = await helper.getStat({
237
- bucket: uploadResult.bucketName,
238
- name: uploadResult.objectName,
465
+ // `storage: 'disk'` spools the payload to `uploadDir` and returns `path` instead of `buffer`; the storage helpers only ever write `buffer`, so it must be read back.
466
+ const modifiedFiles = filesArray.map(file => {
467
+ const buffer = file.buffer ?? (file.path ? (0, node_fs_1.readFileSync)(file.path) : undefined);
468
+ if (!buffer?.length) {
469
+ throw (0, core_1.getError)({
470
+ error: common_2.StaticAssetErrors.FILE_EMPTY,
471
+ message: `Empty file content | name: ${file.originalname}`,
472
+ });
473
+ }
474
+ // Authoritative: `buffer.length`, not the client-declared `size`.
475
+ if (maxBytes !== undefined && buffer.length > maxBytes) {
476
+ throw (0, core_1.getError)({
477
+ error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
478
+ message: `File ${file.originalname} of ${buffer.length} bytes exceeds the maximum of ${maxBytes}`,
479
+ });
480
+ }
481
+ return {
482
+ originalName: file.originalname,
483
+ mimetype: file.mimetype,
484
+ buffer,
485
+ size: file.size,
486
+ encoding: file.encoding,
487
+ folderPath: folderPath ?? undefined,
488
+ };
239
489
  });
240
- const { data: createdMetaLink } = metaLink.createMetaLink
241
- ? await metaLink.createMetaLink({
490
+ // Built per request: the hook is offered the bucket, which only the route knows. With
491
+ // neither hook nor scope, `normalizeNameFn` stays exactly as configured. Under a scope,
492
+ // the component's own default is placed inside it; an application hook's key is
493
+ // checked, never rewritten, because the application may record that key elsewhere.
494
+ const normalizeNameFn = resolveObjectName || keyPrefix !== undefined
495
+ ? (naming) => {
496
+ if (isScopedDefaultNaming &&
497
+ !helper.isValidSegment({ segment: naming.file.originalName })) {
498
+ throw (0, core_1.getError)({ message: '[upload] Invalid original file name' });
499
+ }
500
+ const defaultKey = options?.normalizeNameFn
501
+ ? options.normalizeNameFn(naming)
502
+ : `${keyPrefix ?? ''}${defaultObjectName(naming)}`;
503
+ const key = resolveObjectName
504
+ ? resolveObjectName({
505
+ bucket: { name: bucketName },
506
+ file: naming.file,
507
+ defaultKey,
508
+ })
509
+ : defaultKey;
510
+ if (!(0, key_scope_1.isKeyInScope)({ key, keyPrefix })) {
511
+ throw (0, core_1.getError)({
512
+ error: common_2.StaticAssetErrors.OBJECT_KEY_OUT_OF_SCOPE,
513
+ message: `Object key is outside the key prefix of this controller | key: ${key} | keyPrefix: ${keyPrefix}`,
514
+ });
515
+ }
516
+ return key;
517
+ }
518
+ : options?.normalizeNameFn;
519
+ uploaded = await helper.upload({
520
+ bucket: { name: bucketName },
521
+ files: modifiedFiles,
522
+ normalizeNameFn,
523
+ normalizeLinkFn,
524
+ // Without this the helper re-validates against its own hard default of 2, so an app configured for a deeper tree spools the body and only then fails inside the helper.
525
+ maxFolderDepth: objectKeyMaxDepth,
526
+ });
527
+ }
528
+ finally {
529
+ this.removeSpoolFiles({ paths: spoolPaths });
530
+ }
531
+ if (!useMetaLink || !metaLink) {
532
+ return ctx.json(uploaded, common_1.HTTP.ResultCodes.RS_2.Ok);
533
+ }
534
+ const results = [];
535
+ for (const uploadResult of uploaded) {
536
+ try {
537
+ const fileStat = await helper.getStat({
538
+ bucket: uploadResult.bucket,
539
+ object: uploadResult.object,
540
+ });
541
+ const createdMetaLink = await (0, meta_link_1.createMetaLinkRow)({
542
+ metaLink,
543
+ helper,
544
+ storage,
242
545
  uploadResult,
243
546
  fileStat,
244
547
  query,
245
- })
246
- : await metaLink.repository.create({
247
- data: {
248
- bucketName: uploadResult.bucketName,
249
- objectName: uploadResult.objectName,
250
- link: uploadResult.link,
251
- mimetype: fileStat.metadata?.['mimetype'],
252
- size: fileStat.size,
253
- etag: fileStat.etag,
254
- metadata: fileStat.metadata,
255
- storageType: storage,
256
- isSynced: true,
257
- principalId: query.principalId ? String(query.principalId) : undefined,
258
- principalType: query.principalType
259
- ? String(query.principalType)
260
- : undefined,
261
- variant: query.variant ? String(query.variant) : undefined,
262
- },
263
548
  });
264
- results.push({ ...uploadResult, metaLink: createdMetaLink });
265
- }
266
- catch (error) {
267
- this.logger
268
- .for('UPLOAD')
269
- .error('Failed to create MetaLink | objectName: %s | Error: %s', uploadResult.objectName, error);
270
- results.push({
271
- ...uploadResult,
272
- metaLink: null,
273
- // A CODE, never the driver text. This handler returns 200, so it bypasses the
274
- // error middleware and `database.handler` - the two places that strip
275
- // `detail`/`table`/`constraint` - and shipped raw constraint names to the client.
276
- // The real error is already logged in full immediately above.
277
- metaLinkError: 'META_LINK_CREATE_FAILED',
278
- });
279
- }
280
- }
281
- return ctx.json(results, common_1.HTTP.ResultCodes.RS_2.Ok);
282
- },
283
- });
284
- this.bindRoute({
285
- configs: { ...base_definition_1.StaticAssetDefinitions.DELETE_BUCKET, ...routes?.deleteBucket },
286
- }).to({
287
- handler: async (ctx) => {
288
- const { bucketName } = ctx.req.valid('param');
289
- if (!helper.isValidName(bucketName)) {
290
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
291
- }
292
- const isRemovedBucket = await helper.removeBucket({
293
- name: bucketName,
294
- });
295
- return ctx.json({ isDeleted: isRemovedBucket }, common_1.HTTP.ResultCodes.RS_2.Ok);
296
- },
297
- });
298
- this.bindRoute({
299
- configs: { ...base_definition_1.StaticAssetDefinitions.DELETE_OBJECT, ...routes?.deleteObject },
300
- }).to({
301
- handler: async (ctx) => {
302
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
303
- const objectName = readObjectName(rawObjectName);
304
- if (!helper.isValidName(bucketName)) {
305
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
306
- }
307
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
308
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
309
- }
310
- await helper.removeObject({ bucket: bucketName, name: objectName });
311
- if (!useMetaLink || !metaLink) {
312
- return ctx.json({ success: true }, common_1.HTTP.ResultCodes.RS_2.Ok);
313
- }
314
- metaLink.repository
315
- .deleteAll({
316
- where: {
317
- bucketName,
318
- objectName,
319
- },
320
- })
321
- .then(() => {
322
- this.logger
323
- .for('DELETE_OBJECT')
324
- .info('Successfully to delete MetaLink for %s/%s', bucketName, objectName);
325
- })
326
- .catch(error => {
327
- this.logger
328
- .for('DELETE_OBJECT')
329
- .error('Failed to delete MetaLink | bucket: %s | objectName: %s | Error: %s', bucketName, objectName, error);
330
- });
331
- return ctx.json({ success: true }, common_1.HTTP.ResultCodes.RS_2.Ok);
332
- },
333
- });
334
- this.bindRoute({
335
- configs: { ...base_definition_1.StaticAssetDefinitions.LIST_OBJECTS, ...routes?.listObjects },
336
- }).to({
337
- handler: async (ctx) => {
338
- const { bucketName } = ctx.req.valid('param');
339
- const { prefix, recursive, maxKeys } = ctx.req.valid('query');
340
- if (!helper.isValidName(bucketName)) {
341
- throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
342
- }
343
- // A NaN or 0 maxKeys is silently treated as "unlimited" by the storage backends, so an unparsable value must be rejected instead of forwarded.
344
- let resolvedMaxKeys;
345
- if (maxKeys !== undefined) {
346
- resolvedMaxKeys = Number(maxKeys);
347
- if (!Number.isInteger(resolvedMaxKeys) || resolvedMaxKeys < 1) {
348
- throw (0, core_1.getError)({
349
- error: common_2.StaticAssetErrors.MAX_KEYS_INVALID,
350
- message: `Invalid maxKeys | Expected a positive integer | value: ${maxKeys}`,
351
- });
549
+ results.push({ ...uploadResult, metaLink: { data: createdMetaLink } });
550
+ }
551
+ catch (error) {
552
+ this.logger
553
+ .for('UPLOAD')
554
+ .error('Failed to create MetaLink | objectName: %s | Error: %s', uploadResult.object.key, error);
555
+ results.push({
556
+ ...uploadResult,
557
+ // A CODE, never the driver text. This handler returns 200, so it bypasses the
558
+ // error middleware and `database.handler` - the two places that strip
559
+ // `detail`/`table`/`constraint` - and shipped raw constraint names to the client.
560
+ // The real error is already logged in full immediately above.
561
+ metaLink: { error: common_2.META_LINK_CREATE_FAILED },
562
+ });
563
+ }
352
564
  }
353
- }
354
- const objects = await helper.listObjects({
355
- bucket: bucketName,
356
- prefix,
357
- useRecursive: recursive === 'true',
358
- maxKeys: resolvedMaxKeys,
359
- });
360
- return ctx.json(objects, common_1.HTTP.ResultCodes.RS_2.Ok);
361
- },
362
- });
363
- if (useMetaLink && metaLink) {
565
+ return ctx.json(results, common_1.HTTP.ResultCodes.RS_2.Ok);
566
+ },
567
+ });
568
+ }
569
+ const deleteObjectRoute = (0, route_options_1.readRouteOverride)({ route: routes?.deleteObject });
570
+ if (deleteObjectRoute.isEnabled) {
364
571
  this.bindRoute({
365
- configs: { ...base_definition_1.StaticAssetDefinitions.RECREATE_METALINK, ...routes?.recreateMetaLink },
572
+ configs: { ...definitions.DELETE_OBJECT, ...deleteObjectRoute.override },
366
573
  }).to({
367
574
  handler: async (ctx) => {
368
- const { bucketName, objectName: rawObjectName } = ctx.req.valid('param');
369
- const objectName = readObjectName(rawObjectName);
370
- if (!helper.isValidName(bucketName)) {
575
+ const params = ctx.req.valid('param');
576
+ const bucketName = await resolveBucket({
577
+ configured: bucket,
578
+ param: params.bucketName,
579
+ });
580
+ const objectName = readObjectName(params.objectName);
581
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
371
582
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
372
583
  }
373
- if (!helper.isValidPath(objectName, { maxDepth: maxFolderDepth })) {
584
+ if (!helper.isValidObjectKey({
585
+ object: { key: objectName },
586
+ maxDepth: objectKeyMaxDepth,
587
+ })) {
374
588
  throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
375
589
  }
376
- const fileStat = await helper.getStat({
377
- bucket: bucketName,
378
- name: objectName,
379
- });
380
- const link = options?.normalizeLinkFn
381
- ? options.normalizeLinkFn({ bucketName, normalizeName: objectName })
382
- : `${normalizedBasePath}/buckets/${bucketName}/objects/${encodeObjectPath(objectName)}`;
383
- const existing = await metaLink.repository.findOne({
384
- filter: {
385
- where: {
386
- bucketName,
387
- objectName,
388
- },
389
- },
390
- });
391
- if (existing) {
392
- await metaLink.repository.updateById({
393
- id: existing.id,
394
- data: {
395
- link,
396
- mimetype: fileStat.metadata?.['mimetype'],
397
- size: fileStat.size,
398
- etag: fileStat.etag,
399
- metadata: fileStat.metadata,
400
- storageType: storage,
401
- isSynced: true,
402
- },
590
+ // Before any storage call: a key outside the scope must not even be probed.
591
+ (0, key_scope_1.assertKeyInScope)({ key: objectName, keyPrefix });
592
+ // Deliberately idempotent: S3 answers 200 for a key that was never there, and consumers
593
+ // already depend on it. `disk` throws instead, so the two are reconciled here, not below.
594
+ try {
595
+ await helper.removeObject({
596
+ bucket: { name: bucketName },
597
+ object: { key: objectName },
403
598
  });
404
- const updatedMetaLink = await metaLink.repository.findById({ id: existing.id });
405
- return ctx.json({ success: true, metaLink: updatedMetaLink }, common_1.HTTP.ResultCodes.RS_2.Ok);
406
599
  }
407
- const createdMetaLink = await metaLink.repository.create({
408
- data: {
600
+ catch (error) {
601
+ if (!(0, ignis_helpers_1.isNotFoundError)({ error })) {
602
+ throw error;
603
+ }
604
+ }
605
+ if (!useMetaLink || !metaLink) {
606
+ return ctx.json({ success: true }, common_1.HTTP.ResultCodes.RS_2.Ok);
607
+ }
608
+ const deleteRepository = await (0, common_1.resolveValueAsync)({ value: metaLink.repository });
609
+ deleteRepository
610
+ .deleteAll({
611
+ where: {
409
612
  bucketName,
410
613
  objectName,
411
- link,
412
- mimetype: fileStat.metadata?.['mimetype'],
413
- size: fileStat.size,
414
- etag: fileStat.etag,
415
- metadata: fileStat.metadata,
416
- storageType: storage,
417
- isSynced: true,
418
614
  },
615
+ })
616
+ .then(() => {
617
+ this.logger
618
+ .for('DELETE_OBJECT')
619
+ .info('Successfully to delete MetaLink for %s/%s', bucketName, objectName);
620
+ })
621
+ .catch(error => {
622
+ this.logger
623
+ .for('DELETE_OBJECT')
624
+ .error('Failed to delete MetaLink | bucket: %s | objectName: %s | Error: %s', bucketName, objectName, error);
625
+ });
626
+ return ctx.json({ success: true }, common_1.HTTP.ResultCodes.RS_2.Ok);
627
+ },
628
+ });
629
+ }
630
+ const listObjectsRoute = (0, route_options_1.readRouteOverride)({ route: routes?.listObjects });
631
+ if (listObjectsRoute.isEnabled) {
632
+ this.bindRoute({
633
+ configs: { ...definitions.LIST_OBJECTS, ...listObjectsRoute.override },
634
+ }).to({
635
+ handler: async (ctx) => {
636
+ // A configured bucket drops `params` from this route entirely, so it is read only when it exists.
637
+ const bucketParam = hasConfiguredBucket
638
+ ? undefined
639
+ : ctx.req.valid('param').bucketName;
640
+ const bucketName = await resolveBucket({ configured: bucket, param: bucketParam });
641
+ const { prefix, recursive, maxKeys } = ctx.req.valid('query');
642
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
643
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
644
+ }
645
+ // A NaN or 0 maxKeys is silently treated as "unlimited" by the storage backends, so an unparsable value must be rejected instead of forwarded.
646
+ let resolvedMaxKeys;
647
+ if (maxKeys !== undefined) {
648
+ resolvedMaxKeys = Number(maxKeys);
649
+ if (!Number.isInteger(resolvedMaxKeys) || resolvedMaxKeys < 1) {
650
+ throw (0, core_1.getError)({
651
+ error: common_2.StaticAssetErrors.MAX_KEYS_INVALID,
652
+ message: `Invalid maxKeys | Expected a positive integer | value: ${maxKeys}`,
653
+ });
654
+ }
655
+ }
656
+ const scoped = (0, key_scope_1.resolveScopedListPrefix)({ prefix, keyPrefix });
657
+ if (scoped.isOutOfScope) {
658
+ return ctx.json([], common_1.HTTP.ResultCodes.RS_2.Ok);
659
+ }
660
+ const objects = await helper.listObjects({
661
+ bucket: { name: bucketName },
662
+ prefix: scoped.prefix,
663
+ useRecursive: recursive === 'true',
664
+ maxKeys: resolvedMaxKeys,
419
665
  });
420
- return ctx.json({ success: true, metaLink: createdMetaLink.data }, common_1.HTTP.ResultCodes.RS_2.Ok);
666
+ return ctx.json(objects, common_1.HTTP.ResultCodes.RS_2.Ok);
421
667
  },
422
668
  });
423
669
  }
670
+ if (directUpload) {
671
+ if (!hasConfiguredBucket) {
672
+ throw (0, core_1.getError)({
673
+ message: `[${name}] directUpload needs controller.bucket | a policy names one bucket, and a bucket in the URL is a bucket the caller chooses`,
674
+ });
675
+ }
676
+ const pendingPrefix = directUpload.pendingPrefix ?? common_2.DEFAULT_PENDING_PREFIX;
677
+ // The scope rides inside the pending key, so the commit, which strips `pendingPrefix`, lands inside it.
678
+ const policyKeyPrefix = `${pendingPrefix}${keyPrefix ?? ''}`;
679
+ const uploadPolicyRoute = (0, route_options_1.readRouteOverride)({ route: routes?.uploadPolicy });
680
+ if (uploadPolicyRoute.isEnabled) {
681
+ this.bindRoute({
682
+ configs: { ...definitions.UPLOAD_POLICY, ...uploadPolicyRoute.override },
683
+ }).to({
684
+ handler: async (ctx) => {
685
+ if (!(await directUpload.authorize({ context: ctx }))) {
686
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.UPLOAD_NOT_AUTHORIZED });
687
+ }
688
+ const { files } = ctx.req.valid('json');
689
+ const bucketName = await resolveBucket({ configured: bucket });
690
+ const policies = [];
691
+ for (const file of files) {
692
+ if (file.size !== undefined && file.size > directUpload.maxBytes) {
693
+ throw (0, core_1.getError)({
694
+ error: common_2.StaticAssetErrors.UPLOAD_TOO_LARGE,
695
+ message: `File '${file.fileName}' is ${file.size} bytes, over the ${directUpload.maxBytes} the policy allows`,
696
+ });
697
+ }
698
+ // Generated, never taken from the caller: a key the caller picks is a key the caller
699
+ // can collide with, and the pending object is the one thing a policy authorizes.
700
+ const objectName = `${policyKeyPrefix}${core_1.UuidHelper.getInstance().v4()}/${file.fileName}`;
701
+ const policy = await helper.presignPost({
702
+ bucket: { name: bucketName },
703
+ keyPrefix: policyKeyPrefix,
704
+ maxBytes: directUpload.maxBytes,
705
+ contentType: file.contentType,
706
+ expiresIn: directUpload.expiresIn,
707
+ });
708
+ policies.push({
709
+ ...policy,
710
+ objectName,
711
+ commitToken: (0, common_3.buildCommitToken)({
712
+ payload: {
713
+ bucket: bucketName,
714
+ key: objectName,
715
+ expiresAt: new Date(policy.expiresAt).getTime(),
716
+ },
717
+ secretKey: directUpload.secretKey,
718
+ }),
719
+ });
720
+ }
721
+ return ctx.json(policies, common_1.HTTP.ResultCodes.RS_2.Ok);
722
+ },
723
+ });
724
+ }
725
+ const uploadCommitRoute = (0, route_options_1.readRouteOverride)({ route: routes?.uploadCommit });
726
+ if (uploadCommitRoute.isEnabled) {
727
+ this.bindRoute({
728
+ configs: { ...definitions.UPLOAD_COMMIT, ...uploadCommitRoute.override },
729
+ }).to({
730
+ handler: async (ctx) => {
731
+ const commitQuery = ctx.req.query();
732
+ refuseLabelsInQuery({ query: commitQuery, names: COMMIT_LABEL_NAMES });
733
+ refuseLabelsInQuery({
734
+ query: commitQuery,
735
+ names: ['folderPath'],
736
+ reason: 'folderPath was decided when the upload policy was issued; the commit route cannot move the object',
737
+ });
738
+ const { commitToken, ...labels } = ctx.req.valid('json');
739
+ // Verified BEFORE any storage call. That is what keeps this route from being a name
740
+ // prober: a forged token fails without a round trip, so timing says nothing.
741
+ const payload = (0, common_3.readCommitToken)({
742
+ token: commitToken,
743
+ secretKey: directUpload.secretKey,
744
+ });
745
+ // Labels, not authorization - the token deliberately carries no business fields, so
746
+ // they travel beside it rather than inside it.
747
+ const query = readUploadLabels({ fields: labels });
748
+ const bucketRef = { name: payload.bucket };
749
+ const pendingObject = { key: payload.key };
750
+ const object = { key: payload.key.slice(pendingPrefix.length) };
751
+ // A token another controller signed with the same secret must not land outside this scope.
752
+ (0, key_scope_1.assertKeyInScope)({ key: object.key, keyPrefix });
753
+ // Before the copy, so a hook that throws leaves the object under the pending prefix
754
+ // for a lifecycle rule, rather than stranding it at the final key.
755
+ await directUpload.onCommit?.({ bucket: bucketRef, pendingObject, object });
756
+ await helper.copyObject({
757
+ bucket: bucketRef,
758
+ source: pendingObject,
759
+ destination: object,
760
+ });
761
+ // The copy IS the commit; removing the temporary object is cleanup. A failure here
762
+ // leaves one object for the lifecycle rule, and failing the request would tell the
763
+ // caller their upload did not work when it did.
764
+ await helper
765
+ .removeObject({ bucket: bucketRef, object: pendingObject })
766
+ .catch(error => {
767
+ this.logger
768
+ .for('UPLOAD_COMMIT')
769
+ .warn('Committed, but the pending object remains | key: %s | error: %s', pendingObject.key, error);
770
+ });
771
+ const link = (0, exports.buildObjectLink)({
772
+ basePath: normalizedBasePath,
773
+ bucket: bucketRef,
774
+ object,
775
+ hasConfiguredBucket,
776
+ rawObjectPath,
777
+ });
778
+ // The two paths have to agree: without this a client reading `metaLink` off the
779
+ // response renders an empty state with no error - the failure nobody reports.
780
+ let metaLinkResult;
781
+ if (useMetaLink && metaLink) {
782
+ try {
783
+ const fileStat = await helper.getStat({ bucket: bucketRef, object });
784
+ const data = await (0, meta_link_1.createMetaLinkRow)({
785
+ metaLink,
786
+ helper,
787
+ storage,
788
+ uploadResult: (0, meta_link_1.buildStoredUploadResult)({
789
+ helper,
790
+ bucket: bucketRef,
791
+ object,
792
+ link,
793
+ fileStat,
794
+ }),
795
+ fileStat,
796
+ query,
797
+ });
798
+ metaLinkResult = { data };
799
+ }
800
+ catch (error) {
801
+ // The object IS committed by now. A 500 would send the caller back with a token
802
+ // whose source no longer exists, so this rides the body as a code, like UPLOAD.
803
+ this.logger
804
+ .for('UPLOAD_COMMIT')
805
+ .error('Failed to create MetaLink | objectName: %s | Error: %s', object.key, error);
806
+ metaLinkResult = { error: common_2.META_LINK_CREATE_FAILED };
807
+ }
808
+ }
809
+ return ctx.json({
810
+ bucket: bucketRef,
811
+ object,
812
+ link,
813
+ ...(metaLinkResult === undefined ? {} : { metaLink: metaLinkResult }),
814
+ }, common_1.HTTP.ResultCodes.RS_2.Ok);
815
+ },
816
+ });
817
+ }
818
+ }
819
+ if (useMetaLink && metaLink) {
820
+ const recreateMetaLinkRoute = (0, route_options_1.readRouteOverride)({ route: routes?.recreateMetaLink });
821
+ if (recreateMetaLinkRoute.isEnabled) {
822
+ this.bindRoute({
823
+ configs: { ...definitions.RECREATE_METALINK, ...recreateMetaLinkRoute.override },
824
+ }).to({
825
+ handler: async (ctx) => {
826
+ // This route takes no labels anywhere: one in the query would land an unlabelled row.
827
+ refuseLabelsInQuery({
828
+ query: ctx.req.query(),
829
+ reason: 'This route recreates a MetaLink for an existing object and takes no labels',
830
+ });
831
+ const params = ctx.req.valid('param');
832
+ const bucketName = await resolveBucket({
833
+ configured: bucket,
834
+ param: params.bucketName,
835
+ });
836
+ const objectName = readObjectName(params.objectName);
837
+ if (!helper.isValidBucketName({ bucket: { name: bucketName } })) {
838
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.BUCKET_NAME_INVALID });
839
+ }
840
+ if (!helper.isValidObjectKey({
841
+ object: { key: objectName },
842
+ maxDepth: objectKeyMaxDepth,
843
+ })) {
844
+ throw (0, core_1.getError)({ error: common_2.StaticAssetErrors.OBJECT_NAME_INVALID });
845
+ }
846
+ // Before any storage call: a key outside the scope must not even be probed.
847
+ (0, key_scope_1.assertKeyInScope)({ key: objectName, keyPrefix });
848
+ const bucketRef = { name: bucketName };
849
+ const object = { key: objectName };
850
+ const fileStat = await helper.getStat({ bucket: bucketRef, object });
851
+ const link = options?.normalizeLinkFn
852
+ ? options.normalizeLinkFn({ bucket: bucketRef, object })
853
+ : (0, exports.buildObjectLink)({
854
+ basePath: normalizedBasePath,
855
+ bucket: bucketRef,
856
+ object,
857
+ hasConfiguredBucket,
858
+ rawObjectPath,
859
+ });
860
+ const refreshed = await (0, meta_link_1.refreshMetaLinkRows)({
861
+ metaLink,
862
+ helper,
863
+ bucket: bucketRef,
864
+ object,
865
+ link,
866
+ fileStat,
867
+ });
868
+ if (refreshed.count > 0) {
869
+ return ctx.json({
870
+ success: true,
871
+ action: common_3.MetaLinkRecreateActions.REFRESHED,
872
+ count: refreshed.count,
873
+ metaLink: refreshed.data[0],
874
+ metaLinks: refreshed.data,
875
+ }, common_1.HTTP.ResultCodes.RS_2.Ok);
876
+ }
877
+ // Not atomic with the refresh above: two concurrent calls may both create, which the
878
+ // table allows - rows per pair are deliberately not unique.
879
+ const created = await (0, meta_link_1.createMetaLinkRow)({
880
+ metaLink,
881
+ helper,
882
+ storage,
883
+ uploadResult: (0, meta_link_1.buildStoredUploadResult)({
884
+ helper,
885
+ bucket: bucketRef,
886
+ object,
887
+ link,
888
+ fileStat,
889
+ }),
890
+ fileStat,
891
+ query: {},
892
+ });
893
+ return ctx.json({
894
+ success: true,
895
+ action: common_3.MetaLinkRecreateActions.CREATED,
896
+ count: 1,
897
+ metaLink: created,
898
+ metaLinks: [created],
899
+ }, common_1.HTTP.ResultCodes.RS_2.Ok);
900
+ },
901
+ });
902
+ }
903
+ }
904
+ // Last, so a built-in route always wins a path collision with an application's own.
905
+ defineExtraRoutes?.({ controller: this, helper, basePath: normalizedBasePath });
424
906
  }
425
907
  };
426
908
  GeneratedStaticAssetController = __decorate([