@decohere/http 0.0.1-dev.125 → 0.0.1-dev.5

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 (292) hide show
  1. package/package.json +4 -13
  2. package/src/app/app.ts +147 -0
  3. package/src/app/module.ts +19 -0
  4. package/src/auth/ability.ts +56 -0
  5. package/src/auth/auth.config.ts +34 -0
  6. package/src/auth/auth.extensions.ts +115 -0
  7. package/src/auth/auth.types.ts +124 -0
  8. package/src/auth/casl-authorization.service.ts +62 -0
  9. package/{types/src/auth/principal-provider.d.ts → src/auth/principal-provider.ts} +13 -5
  10. package/src/auth/safe-outbound-url.ts +117 -0
  11. package/src/auth/token-service.ts +192 -0
  12. package/src/builder/builder.ts +118 -0
  13. package/{es6/src/const.js → src/const.ts} +25 -17
  14. package/src/health/health.controller.ts +34 -0
  15. package/src/health/health.extensions.ts +53 -0
  16. package/src/health/readiness.service.ts +33 -0
  17. package/{types/src/health/readiness.types.d.ts → src/health/readiness.types.ts} +4 -8
  18. package/src/hosting/hosted-service-host.ts +102 -0
  19. package/src/hosting/hosted-service.ts +31 -0
  20. package/src/http/http.compression.ts +38 -0
  21. package/src/http/http.extensions.ts +244 -0
  22. package/{types/src/http/http.options.store.d.ts → src/http/http.options.store.ts} +11 -1
  23. package/src/http/http.options.ts +41 -0
  24. package/{types/src/http/http.security.d.ts → src/http/http.security.ts} +16 -10
  25. package/src/http/http.static.ts +359 -0
  26. package/{es6/src/index.js → src/index.ts} +1 -0
  27. package/src/openapi/index.ts +22 -0
  28. package/src/openapi/openapi.generator.ts +78 -0
  29. package/src/openapi/openapi.types.ts +60 -0
  30. package/src/openapi/request-schema.ts +155 -0
  31. package/src/openapi/request-validator.ts +95 -0
  32. package/src/providers/fastify.provider.ts +454 -0
  33. package/src/routing/anvil.types.ts +150 -0
  34. package/src/routing/decorators/argument.decorator.ts +33 -0
  35. package/src/routing/decorators/auth.decorator.ts +79 -0
  36. package/src/routing/decorators/controller.decorator.ts +9 -0
  37. package/src/routing/decorators/field.decorator.ts +15 -0
  38. package/src/routing/decorators/request.decorator.ts +34 -0
  39. package/src/routing/decorators/response.decorator.ts +18 -0
  40. package/src/routing/decorators/route.decorator.ts +52 -0
  41. package/src/routing/filters/exception-filter-host.ts +90 -0
  42. package/src/routing/filters/exception-filter.ts +29 -0
  43. package/src/routing/middleware/http-access-log.middleware.ts +36 -0
  44. package/src/routing/middleware/middleware.ts +26 -0
  45. package/src/routing/middleware/request-id.ts +18 -0
  46. package/src/routing/path.util.ts +38 -0
  47. package/src/routing/pipes/pipes.ts +132 -0
  48. package/src/routing/services/route-argument-resolver.ts +92 -0
  49. package/src/routing/services/route-registry.ts +229 -0
  50. package/src/routing/services/router.ts +166 -0
  51. package/src/sse/sse.decorator.ts +41 -0
  52. package/src/sse/sse.encoder.ts +86 -0
  53. package/src/sse/sse.types.ts +47 -0
  54. package/src/sse/sse.wire.ts +34 -0
  55. package/src/testing/boot-module.ts +63 -0
  56. package/src/testing/sse-reader.ts +120 -0
  57. package/tests/app.hosted-services.spec.ts +292 -0
  58. package/tests/app.shutdown.integration.spec.ts +160 -0
  59. package/tests/app.spec.ts +132 -0
  60. package/tests/auth.ability.spec.ts +48 -0
  61. package/tests/auth.decorators.spec.ts +20 -0
  62. package/tests/auth.fail-closed.pentest.spec.ts +77 -0
  63. package/tests/auth.integration.spec.ts +234 -0
  64. package/tests/auth.jwks-cache.spec.ts +52 -0
  65. package/tests/auth.missing-auth.pentest.spec.ts +56 -0
  66. package/tests/auth.oidc-ssrf.pentest.spec.ts +72 -0
  67. package/tests/auth.oidc.integration.spec.ts +161 -0
  68. package/tests/auth.token-service.spec.ts +251 -0
  69. package/tests/builder.spec.ts +12 -0
  70. package/tests/health.integration.spec.ts +111 -0
  71. package/tests/helpers/listen-ephemeral.ts +13 -0
  72. package/tests/helpers/local-oidc-server.ts +100 -0
  73. package/tests/http.access-log.integration.spec.ts +111 -0
  74. package/tests/http.boolean-query.integration.spec.ts +59 -0
  75. package/tests/http.compression.integration.spec.ts +604 -0
  76. package/tests/http.date-query.integration.spec.ts +71 -0
  77. package/tests/http.global-prefix.integration.spec.ts +284 -0
  78. package/tests/http.number-query.integration.spec.ts +65 -0
  79. package/tests/http.pino-roll-hmr.integration.spec.ts +79 -0
  80. package/tests/http.pipeline-order.integration.spec.ts +95 -0
  81. package/tests/http.request-cleanup.integration.spec.ts +57 -0
  82. package/tests/http.request-id-scope.pentest.spec.ts +50 -0
  83. package/tests/http.security.integration.spec.ts +106 -0
  84. package/tests/http.static.integration.spec.ts +847 -0
  85. package/tests/http.static.spec.ts +111 -0
  86. package/tests/http.surface.integration.spec.ts +167 -0
  87. package/tests/openapi.integration.spec.ts +193 -0
  88. package/tests/openapi.validator-cache.integration.spec.ts +55 -0
  89. package/tests/routing.decorators.spec.ts +15 -0
  90. package/tests/routing.registry.spec.ts +44 -0
  91. package/tests/routing.resolver.spec.ts +92 -0
  92. package/tests/sse.integration.spec.ts +421 -0
  93. package/tests/sse.wire.spec.ts +33 -0
  94. package/LICENSE +0 -15
  95. package/commonjs/src/app/app.js +0 -166
  96. package/commonjs/src/app/index.js +0 -19
  97. package/commonjs/src/app/module.js +0 -17
  98. package/commonjs/src/auth/ability.js +0 -54
  99. package/commonjs/src/auth/auth.config.js +0 -23
  100. package/commonjs/src/auth/auth.extensions.js +0 -106
  101. package/commonjs/src/auth/auth.types.js +0 -65
  102. package/commonjs/src/auth/casl-authorization.service.js +0 -36
  103. package/commonjs/src/auth/index.js +0 -23
  104. package/commonjs/src/auth/principal-provider.js +0 -19
  105. package/commonjs/src/auth/safe-outbound-url.js +0 -102
  106. package/commonjs/src/auth/token-service.js +0 -151
  107. package/commonjs/src/builder/builder.js +0 -95
  108. package/commonjs/src/builder/index.js +0 -18
  109. package/commonjs/src/const.js +0 -87
  110. package/commonjs/src/health/health.controller.js +0 -75
  111. package/commonjs/src/health/health.extensions.js +0 -50
  112. package/commonjs/src/health/index.js +0 -21
  113. package/commonjs/src/health/readiness.service.js +0 -67
  114. package/commonjs/src/health/readiness.types.js +0 -7
  115. package/commonjs/src/hosting/hosted-service-host.js +0 -88
  116. package/commonjs/src/hosting/hosted-service.js +0 -11
  117. package/commonjs/src/hosting/index.js +0 -19
  118. package/commonjs/src/http/http.compression.js +0 -27
  119. package/commonjs/src/http/http.extensions.js +0 -269
  120. package/commonjs/src/http/http.options.js +0 -26
  121. package/commonjs/src/http/http.options.store.js +0 -20
  122. package/commonjs/src/http/http.security.js +0 -27
  123. package/commonjs/src/http/http.static.js +0 -248
  124. package/commonjs/src/http/index.js +0 -22
  125. package/commonjs/src/index.js +0 -27
  126. package/commonjs/src/openapi/index.js +0 -45
  127. package/commonjs/src/openapi/openapi.generator.js +0 -103
  128. package/commonjs/src/openapi/openapi.types.js +0 -4
  129. package/commonjs/src/openapi/request-schema.js +0 -187
  130. package/commonjs/src/openapi/request-validator.js +0 -85
  131. package/commonjs/src/providers/fastify.provider.js +0 -391
  132. package/commonjs/src/providers/index.js +0 -18
  133. package/commonjs/src/routing/anvil.types.js +0 -99
  134. package/commonjs/src/routing/decorators/argument.decorator.js +0 -49
  135. package/commonjs/src/routing/decorators/auth.decorator.js +0 -70
  136. package/commonjs/src/routing/decorators/controller.decorator.js +0 -16
  137. package/commonjs/src/routing/decorators/field.decorator.js +0 -25
  138. package/commonjs/src/routing/decorators/index.js +0 -24
  139. package/commonjs/src/routing/decorators/request.decorator.js +0 -30
  140. package/commonjs/src/routing/decorators/response.decorator.js +0 -34
  141. package/commonjs/src/routing/decorators/route.decorator.js +0 -67
  142. package/commonjs/src/routing/filters/exception-filter-host.js +0 -145
  143. package/commonjs/src/routing/filters/exception-filter.js +0 -28
  144. package/commonjs/src/routing/index.js +0 -25
  145. package/commonjs/src/routing/middleware/http-access-log.middleware.js +0 -47
  146. package/commonjs/src/routing/middleware/middleware.js +0 -20
  147. package/commonjs/src/routing/middleware/request-id.js +0 -30
  148. package/commonjs/src/routing/path.util.js +0 -51
  149. package/commonjs/src/routing/pipes/index.js +0 -18
  150. package/commonjs/src/routing/pipes/pipes.js +0 -145
  151. package/commonjs/src/routing/services/index.js +0 -18
  152. package/commonjs/src/routing/services/route-argument-resolver.js +0 -77
  153. package/commonjs/src/routing/services/route-registry.js +0 -194
  154. package/commonjs/src/routing/services/router.js +0 -158
  155. package/commonjs/src/sse/index.js +0 -26
  156. package/commonjs/src/sse/sse.decorator.js +0 -42
  157. package/commonjs/src/sse/sse.encoder.js +0 -73
  158. package/commonjs/src/sse/sse.types.js +0 -36
  159. package/commonjs/src/sse/sse.wire.js +0 -40
  160. package/commonjs/src/testing/boot-module.js +0 -38
  161. package/commonjs/src/testing/index.js +0 -20
  162. package/commonjs/src/testing/sse-reader.js +0 -98
  163. package/es6/src/app/app.js +0 -148
  164. package/es6/src/app/index.js +0 -2
  165. package/es6/src/app/module.js +0 -7
  166. package/es6/src/auth/ability.js +0 -45
  167. package/es6/src/auth/auth.config.js +0 -19
  168. package/es6/src/auth/auth.extensions.js +0 -83
  169. package/es6/src/auth/auth.types.js +0 -35
  170. package/es6/src/auth/casl-authorization.service.js +0 -40
  171. package/es6/src/auth/index.js +0 -6
  172. package/es6/src/auth/principal-provider.js +0 -14
  173. package/es6/src/auth/safe-outbound-url.js +0 -99
  174. package/es6/src/auth/token-service.js +0 -141
  175. package/es6/src/builder/builder.js +0 -85
  176. package/es6/src/builder/index.js +0 -1
  177. package/es6/src/health/health.controller.js +0 -65
  178. package/es6/src/health/health.extensions.js +0 -40
  179. package/es6/src/health/index.js +0 -4
  180. package/es6/src/health/readiness.service.js +0 -57
  181. package/es6/src/health/readiness.types.js +0 -4
  182. package/es6/src/hosting/hosted-service-host.js +0 -81
  183. package/es6/src/hosting/hosted-service.js +0 -8
  184. package/es6/src/hosting/index.js +0 -2
  185. package/es6/src/http/http.compression.js +0 -9
  186. package/es6/src/http/http.extensions.js +0 -259
  187. package/es6/src/http/http.options.js +0 -16
  188. package/es6/src/http/http.options.store.js +0 -13
  189. package/es6/src/http/http.security.js +0 -9
  190. package/es6/src/http/http.static.js +0 -224
  191. package/es6/src/http/index.js +0 -5
  192. package/es6/src/openapi/index.js +0 -3
  193. package/es6/src/openapi/openapi.generator.js +0 -82
  194. package/es6/src/openapi/openapi.types.js +0 -1
  195. package/es6/src/openapi/request-schema.js +0 -160
  196. package/es6/src/openapi/request-validator.js +0 -71
  197. package/es6/src/providers/fastify.provider.js +0 -376
  198. package/es6/src/providers/index.js +0 -1
  199. package/es6/src/routing/anvil.types.js +0 -57
  200. package/es6/src/routing/decorators/argument.decorator.js +0 -28
  201. package/es6/src/routing/decorators/auth.decorator.js +0 -58
  202. package/es6/src/routing/decorators/controller.decorator.js +0 -6
  203. package/es6/src/routing/decorators/field.decorator.js +0 -15
  204. package/es6/src/routing/decorators/index.js +0 -7
  205. package/es6/src/routing/decorators/request.decorator.js +0 -24
  206. package/es6/src/routing/decorators/response.decorator.js +0 -16
  207. package/es6/src/routing/decorators/route.decorator.js +0 -37
  208. package/es6/src/routing/filters/exception-filter-host.js +0 -127
  209. package/es6/src/routing/filters/exception-filter.js +0 -10
  210. package/es6/src/routing/index.js +0 -8
  211. package/es6/src/routing/middleware/http-access-log.middleware.js +0 -37
  212. package/es6/src/routing/middleware/middleware.js +0 -10
  213. package/es6/src/routing/middleware/request-id.js +0 -14
  214. package/es6/src/routing/path.util.js +0 -33
  215. package/es6/src/routing/pipes/index.js +0 -1
  216. package/es6/src/routing/pipes/pipes.js +0 -118
  217. package/es6/src/routing/services/index.js +0 -1
  218. package/es6/src/routing/services/route-argument-resolver.js +0 -67
  219. package/es6/src/routing/services/route-registry.js +0 -184
  220. package/es6/src/routing/services/router.js +0 -148
  221. package/es6/src/sse/index.js +0 -2
  222. package/es6/src/sse/sse.decorator.js +0 -31
  223. package/es6/src/sse/sse.encoder.js +0 -67
  224. package/es6/src/sse/sse.types.js +0 -22
  225. package/es6/src/sse/sse.wire.js +0 -30
  226. package/es6/src/testing/boot-module.js +0 -35
  227. package/es6/src/testing/index.js +0 -2
  228. package/es6/src/testing/sse-reader.js +0 -91
  229. package/types/src/app/app.d.ts +0 -41
  230. package/types/src/app/module.d.ts +0 -15
  231. package/types/src/auth/ability.d.ts +0 -19
  232. package/types/src/auth/auth.config.d.ts +0 -28
  233. package/types/src/auth/auth.extensions.d.ts +0 -53
  234. package/types/src/auth/auth.types.d.ts +0 -73
  235. package/types/src/auth/casl-authorization.service.d.ts +0 -28
  236. package/types/src/auth/safe-outbound-url.d.ts +0 -9
  237. package/types/src/auth/token-service.d.ts +0 -31
  238. package/types/src/builder/builder.d.ts +0 -36
  239. package/types/src/const.d.ts +0 -32
  240. package/types/src/health/health.controller.d.ts +0 -16
  241. package/types/src/health/health.extensions.d.ts +0 -31
  242. package/types/src/health/readiness.service.d.ts +0 -9
  243. package/types/src/hosting/hosted-service-host.d.ts +0 -37
  244. package/types/src/hosting/hosted-service.d.ts +0 -31
  245. package/types/src/http/http.compression.d.ts +0 -32
  246. package/types/src/http/http.extensions.d.ts +0 -79
  247. package/types/src/http/http.options.d.ts +0 -39
  248. package/types/src/http/http.static.d.ts +0 -160
  249. package/types/src/index.d.ts +0 -10
  250. package/types/src/openapi/index.d.ts +0 -6
  251. package/types/src/openapi/openapi.generator.d.ts +0 -6
  252. package/types/src/openapi/openapi.types.d.ts +0 -58
  253. package/types/src/openapi/request-schema.d.ts +0 -21
  254. package/types/src/openapi/request-validator.d.ts +0 -9
  255. package/types/src/providers/fastify.provider.d.ts +0 -56
  256. package/types/src/routing/anvil.types.d.ts +0 -119
  257. package/types/src/routing/decorators/argument.decorator.d.ts +0 -11
  258. package/types/src/routing/decorators/auth.decorator.d.ts +0 -30
  259. package/types/src/routing/decorators/controller.decorator.d.ts +0 -3
  260. package/types/src/routing/decorators/field.decorator.d.ts +0 -2
  261. package/types/src/routing/decorators/request.decorator.d.ts +0 -8
  262. package/types/src/routing/decorators/response.decorator.d.ts +0 -4
  263. package/types/src/routing/decorators/route.decorator.d.ts +0 -7
  264. package/types/src/routing/filters/exception-filter-host.d.ts +0 -19
  265. package/types/src/routing/filters/exception-filter.d.ts +0 -16
  266. package/types/src/routing/middleware/http-access-log.middleware.d.ts +0 -8
  267. package/types/src/routing/middleware/middleware.d.ts +0 -10
  268. package/types/src/routing/middleware/request-id.d.ts +0 -6
  269. package/types/src/routing/path.util.d.ts +0 -9
  270. package/types/src/routing/pipes/pipes.d.ts +0 -21
  271. package/types/src/routing/services/route-argument-resolver.d.ts +0 -10
  272. package/types/src/routing/services/route-registry.d.ts +0 -20
  273. package/types/src/routing/services/router.d.ts +0 -22
  274. package/types/src/sse/sse.decorator.d.ts +0 -17
  275. package/types/src/sse/sse.encoder.d.ts +0 -16
  276. package/types/src/sse/sse.types.d.ts +0 -37
  277. package/types/src/sse/sse.wire.d.ts +0 -9
  278. package/types/src/testing/boot-module.d.ts +0 -34
  279. package/types/src/testing/sse-reader.d.ts +0 -29
  280. /package/{types/src/app/index.d.ts → src/app/index.ts} +0 -0
  281. /package/{types/src/auth/index.d.ts → src/auth/index.ts} +0 -0
  282. /package/{types/src/builder/index.d.ts → src/builder/index.ts} +0 -0
  283. /package/{types/src/health/index.d.ts → src/health/index.ts} +0 -0
  284. /package/{types/src/hosting/index.d.ts → src/hosting/index.ts} +0 -0
  285. /package/{types/src/http/index.d.ts → src/http/index.ts} +0 -0
  286. /package/{types/src/providers/index.d.ts → src/providers/index.ts} +0 -0
  287. /package/{types/src/routing/decorators/index.d.ts → src/routing/decorators/index.ts} +0 -0
  288. /package/{types/src/routing/index.d.ts → src/routing/index.ts} +0 -0
  289. /package/{types/src/routing/pipes/index.d.ts → src/routing/pipes/index.ts} +0 -0
  290. /package/{types/src/routing/services/index.d.ts → src/routing/services/index.ts} +0 -0
  291. /package/{types/src/sse/index.d.ts → src/sse/index.ts} +0 -0
  292. /package/{types/src/testing/index.d.ts → src/testing/index.ts} +0 -0
@@ -1,38 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "bootModule", {
6
- enumerable: true,
7
- get: function() {
8
- return bootModule;
9
- }
10
- });
11
- const _builder = require("../builder/builder.js");
12
- require("../http/http.extensions.js");
13
- const _fastifyprovider = require("../providers/fastify.provider.js");
14
- const _ssereader = require("./sse-reader.js");
15
- async function bootModule(options) {
16
- let builder = _builder.Builder.create().withHttp(options.provider ?? _fastifyprovider.FastifyProvider).withControllers(...options.controllers);
17
- if (options.services) builder = builder.withServices(options.services);
18
- options.configure?.(builder);
19
- const app = await builder.build();
20
- await app.start({
21
- port: 0,
22
- host: "127.0.0.1",
23
- shutdownSignals: false
24
- });
25
- const baseUrl = app.addresses[0];
26
- if (baseUrl == null) {
27
- await app.dispose();
28
- throw new Error("app.start({ port: 0 }) advertised no IPv4 address");
29
- }
30
- const stop = ()=>app.dispose();
31
- return {
32
- app,
33
- baseUrl,
34
- readEvents: (path, readOptions)=>(0, _ssereader.readEventStream)(`${baseUrl}${path}`, readOptions),
35
- stop,
36
- [Symbol.asyncDispose]: stop
37
- };
38
- }
@@ -1,20 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- function _export(target, all) {
6
- for(var name in all)Object.defineProperty(target, name, {
7
- enumerable: true,
8
- get: Object.getOwnPropertyDescriptor(all, name).get
9
- });
10
- }
11
- _export(exports, {
12
- get bootModule () {
13
- return _bootmodule.bootModule;
14
- },
15
- get readEventStream () {
16
- return _ssereader.readEventStream;
17
- }
18
- });
19
- const _bootmodule = require("./boot-module.js");
20
- const _ssereader = require("./sse-reader.js");
@@ -1,98 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", {
3
- value: true
4
- });
5
- Object.defineProperty(exports, "readEventStream", {
6
- enumerable: true,
7
- get: function() {
8
- return readEventStream;
9
- }
10
- });
11
- /** Incremental parser for the `text/event-stream` format. */ class SseParser {
12
- buffer = "";
13
- data = [];
14
- current = {
15
- data: ""
16
- };
17
- hasFields = false;
18
- events = [];
19
- comments = [];
20
- push(chunk) {
21
- this.buffer += chunk;
22
- let newline;
23
- while((newline = this.buffer.search(/\r\n|\r|\n/)) !== -1){
24
- const line = this.buffer.slice(0, newline);
25
- const eol = this.buffer.slice(newline).startsWith("\r\n") ? 2 : 1;
26
- this.buffer = this.buffer.slice(newline + eol);
27
- this.line(line);
28
- }
29
- }
30
- line(line) {
31
- if (line === "") {
32
- if (this.hasFields) {
33
- this.events.push({
34
- ...this.current,
35
- data: this.data.join("\n")
36
- });
37
- }
38
- this.data = [];
39
- this.current = {
40
- data: ""
41
- };
42
- this.hasFields = false;
43
- return;
44
- }
45
- if (line.startsWith(":")) {
46
- this.comments.push(line.slice(1).trim());
47
- return;
48
- }
49
- const colon = line.indexOf(":");
50
- const field = colon === -1 ? line : line.slice(0, colon);
51
- let value = colon === -1 ? "" : line.slice(colon + 1);
52
- if (value.startsWith(" ")) value = value.slice(1);
53
- this.hasFields = true;
54
- if (field === "data") this.data.push(value);
55
- else if (field === "event") this.current.event = value;
56
- else if (field === "id") this.current.id = value;
57
- else if (field === "retry") this.current.retry = Number(value);
58
- }
59
- }
60
- async function readEventStream(url, options = {}) {
61
- const count = options.count ?? 1;
62
- const abort = new AbortController();
63
- const timer = setTimeout(()=>abort.abort(new Error(`No ${count} events from ${url} in time`)), options.timeoutMs ?? 5000);
64
- try {
65
- const response = await fetch(url, {
66
- headers: {
67
- accept: "text/event-stream",
68
- ...options.headers
69
- },
70
- signal: abort.signal
71
- });
72
- const parser = new SseParser();
73
- let ended = false;
74
- if (response.status === 200 && response.body != null) {
75
- const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
76
- while(parser.events.length < count){
77
- const { done, value } = await reader.read();
78
- if (done) {
79
- ended = true;
80
- break;
81
- }
82
- parser.push(value);
83
- }
84
- await reader.cancel();
85
- } else {
86
- await response.body?.cancel();
87
- }
88
- return {
89
- status: response.status,
90
- headers: response.headers,
91
- events: parser.events.slice(0, count),
92
- comments: parser.comments,
93
- ended
94
- };
95
- } finally{
96
- clearTimeout(timer);
97
- }
98
- }
@@ -1,148 +0,0 @@
1
- function _ts_decorate(decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- }
7
- function _ts_metadata(k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- }
10
- function _ts_param(paramIndex, decorator) {
11
- return function(target, key) {
12
- decorator(target, key, paramIndex);
13
- };
14
- }
15
- import { LifetimeScopeToken } from "@decohere/crucible";
16
- import { inject, optional } from "@decohere/crucible/decorators";
17
- import { ConfigurationServiceToken } from "@decohere/core/configuration";
18
- import { scribe } from "@decohere/scribe";
19
- import { HostedServiceHostToken, HttpProviderToken } from "../const.js";
20
- const DEFAULT_PORT = 3000;
21
- const DEFAULT_SHUTDOWN_SIGNALS = [
22
- "SIGTERM",
23
- "SIGINT"
24
- ];
25
- const DEFAULT_SHUTDOWN_TIMEOUT_MS = 10_000;
26
- export class ApiConfiguration {
27
- port;
28
- }
29
- export class App {
30
- container;
31
- configurationService;
32
- httpProvider;
33
- hostedServices;
34
- _addresses = Array.of();
35
- logger = scribe.logger(App.name);
36
- disposing;
37
- disposingContainer = false;
38
- signalHandler;
39
- shutdownSignals = [];
40
- constructor(container, configurationService, httpProvider, hostedServices){
41
- this.container = container;
42
- this.configurationService = configurationService;
43
- this.httpProvider = httpProvider;
44
- this.hostedServices = hostedServices;
45
- }
46
- get addresses() {
47
- return this._addresses;
48
- }
49
- async start(options) {
50
- this.logger.info("Starting app");
51
- // Before HTTP, so a request never reaches a service that has not started.
52
- await this.hostedServices?.start(this.container);
53
- // The app was stopped while a hosted service was still starting.
54
- if (this.disposing != null) return;
55
- const hasHostedServices = this.hostedServices != null && !this.hostedServices.isEmpty;
56
- if (!this.httpProvider) {
57
- if (hasHostedServices) this.stopOnSignals(options);
58
- return;
59
- }
60
- const port = await this.getPort(options);
61
- this.logger.info(`Starting http on port ${port}`);
62
- const startResponse = await this.httpProvider?.start({
63
- port,
64
- ...options?.host != null ? {
65
- host: options.host
66
- } : {}
67
- });
68
- this._addresses = [
69
- ...startResponse.addresses
70
- ];
71
- this.logger.info("Listening on:", this._addresses);
72
- this.stopOnSignals(options);
73
- return startResponse;
74
- }
75
- [Symbol.asyncDispose]() {
76
- // The container holds this App too; when stop() disposes the container it lands
77
- // here, and awaiting our own shutdown would deadlock.
78
- if (this.disposingContainer) return Promise.resolve();
79
- return this.dispose();
80
- }
81
- /** Stop the app. Calling it again (or from a signal) returns the same shutdown. */ dispose() {
82
- this.disposing ??= this.stop();
83
- return this.disposing;
84
- }
85
- async stop() {
86
- // Removed first, so a second Ctrl+C / SIGTERM gets Node's default: exit now.
87
- this.removeSignalHandlers();
88
- this.logger.info("Stopping");
89
- // Hosted services hear the signal at once, so background work winds down while HTTP drains.
90
- this.hostedServices?.abort();
91
- try {
92
- // HTTP first: in-flight requests still need the services (DB pools etc.) that
93
- // disposing the container would close.
94
- await this.httpProvider?.stop();
95
- // Hosted services next, while what they use is still open.
96
- await this.hostedServices?.stop();
97
- } finally{
98
- this.disposingContainer = true;
99
- await this.container[Symbol.asyncDispose]();
100
- await scribe.stop();
101
- }
102
- }
103
- stopOnSignals(options) {
104
- const signals = options?.shutdownSignals ?? DEFAULT_SHUTDOWN_SIGNALS;
105
- const timeoutMs = options?.shutdownTimeoutMs ?? DEFAULT_SHUTDOWN_TIMEOUT_MS;
106
- if (signals === false || signals.length === 0) return;
107
- this.signalHandler = (signal)=>{
108
- this.logger.info(`Received ${signal}; finishing in-flight requests`);
109
- // console.error, not the logger: scribe may be the thing that's stuck.
110
- const timer = setTimeout(()=>{
111
- console.error(`Shutdown did not finish within ${timeoutMs}ms; exiting.`);
112
- process.exit(1);
113
- }, timeoutMs);
114
- timer.unref();
115
- this.dispose().then(()=>clearTimeout(timer), (error)=>{
116
- clearTimeout(timer);
117
- console.error("Shutdown failed:", error);
118
- process.exit(1);
119
- });
120
- };
121
- this.shutdownSignals = signals;
122
- for (const signal of signals)process.on(signal, this.signalHandler);
123
- }
124
- removeSignalHandlers() {
125
- if (!this.signalHandler) return;
126
- for (const signal of this.shutdownSignals)process.off(signal, this.signalHandler);
127
- }
128
- async getPort(options) {
129
- const port = options?.port ?? (await this.configurationService?.get(ApiConfiguration))?.port ?? DEFAULT_PORT;
130
- return port;
131
- }
132
- }
133
- App = _ts_decorate([
134
- _ts_param(0, inject(LifetimeScopeToken)),
135
- _ts_param(1, inject(ConfigurationServiceToken)),
136
- _ts_param(1, optional()),
137
- _ts_param(2, inject(HttpProviderToken)),
138
- _ts_param(2, optional()),
139
- _ts_param(3, inject(HostedServiceHostToken)),
140
- _ts_param(3, optional()),
141
- _ts_metadata("design:type", Function),
142
- _ts_metadata("design:paramtypes", [
143
- typeof LifetimeScope === "undefined" ? Object : LifetimeScope,
144
- typeof IConfigurationService === "undefined" ? Object : IConfigurationService,
145
- typeof HttpProvider === "undefined" ? Object : HttpProvider,
146
- typeof HostedServiceHost === "undefined" ? Object : HostedServiceHost
147
- ])
148
- ], App);
@@ -1,2 +0,0 @@
1
- export * from "./app.js";
2
- export * from "./module.js";
@@ -1,7 +0,0 @@
1
- export class Module {
2
- constructor(data){
3
- Object.assign(this, data);
4
- }
5
- entities;
6
- repositories;
7
- }
@@ -1,45 +0,0 @@
1
- import { AbilityBuilder, createMongoAbility } from "@casl/ability";
2
- /** Root role — expands to CASL `manage`/`all` (covers every future ability). */ export const ADMIN_ROLE = "admin";
3
- /**
4
- * Default CASL ability factory.
5
- *
6
- * - `admin` role → `manage` / `all` (wildcard)
7
- * - otherwise map `principal.scopes` of the form `"action:Subject"` to CASL rules
8
- *
9
- * Pass a custom factory to `withAuth()` (or override the app stub) for product-specific rules.
10
- */ export function defineAbilitiesFor(principal) {
11
- const { can, build } = new AbilityBuilder(createMongoAbility);
12
- if (principal.roles?.includes(ADMIN_ROLE)) {
13
- can("manage", "all");
14
- return build();
15
- }
16
- for (const scope of principal.scopes ?? []){
17
- const [action, subject] = scope.split(":");
18
- if (action && subject) can(action, subject);
19
- }
20
- return build();
21
- }
22
- /**
23
- * Map a raw JWT payload → {@link Principal}.
24
- *
25
- * Merges OAuth `scope` / `scopes` with an IdP `permissions` claim (e.g. Auth0 API permissions).
26
- * Override via `withAuth(..., { principalMapper })` when claims differ.
27
- */ export function createPrincipalFromPayload(payload) {
28
- const sub = typeof payload.sub === "string" ? payload.sub : "";
29
- const fromScopeClaim = typeof payload.scope === "string" ? payload.scope.split(" ").filter(Boolean) : Array.isArray(payload.scopes) ? payload.scopes.filter((s)=>typeof s === "string") : [];
30
- const fromPermissions = Array.isArray(payload.permissions) ? payload.permissions.filter((s)=>typeof s === "string") : [];
31
- const merged = [
32
- ...new Set([
33
- ...fromScopeClaim,
34
- ...fromPermissions
35
- ])
36
- ];
37
- const scopes = merged.length > 0 ? merged : undefined;
38
- const roles = Array.isArray(payload.roles) ? payload.roles.filter((r)=>typeof r === "string") : undefined;
39
- return {
40
- ...payload,
41
- sub,
42
- roles: roles && roles.length > 0 ? roles : undefined,
43
- scopes
44
- };
45
- }
@@ -1,19 +0,0 @@
1
- /**
2
- * Configuration for the TokenService, read from the `auth` section of the
3
- * application config file. Exactly one of the discriminant fields must be set.
4
- *
5
- * Production → use `jwksUrl`, `publicKey`, or `secret`.
6
- * Development → use `devStatic` (only works when NODE_ENV=development).
7
- */ export class AuthConfig {
8
- /** OIDC discovery URL — the issuer base URL, e.g. https://accounts.google.com.
9
- * The framework appends `/.well-known/openid-configuration` automatically.
10
- * Use this instead of `jwksUrl` for any standard OIDC-compliant IdP.
11
- */ discoveryUrl;
12
- /** JWKS endpoint URL (RS256 / ES256 via remote key set). */ jwksUrl;
13
- /** PEM-encoded public key or JWK JSON string (RS256 / ES256 static key). */ publicKey;
14
- algorithms;
15
- /** Shared secret for HS256. */ secret;
16
- /** Issuer claim to validate (all non-devStatic variants). */ issuer;
17
- /** Audience claim to validate (all non-devStatic variants). */ audience;
18
- /** Static principal injected for every request (development only). */ devStatic;
19
- }
@@ -1,83 +0,0 @@
1
- function _ts_decorate(decorators, target, key, desc) {
2
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
- else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
- return c > 3 && r && Object.defineProperty(target, key, r), r;
6
- }
7
- function _ts_metadata(k, v) {
8
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
- }
10
- import { extension } from "@decohere/core/extensibility";
11
- import { Builder } from "../builder/builder.js";
12
- import { AuthorizationServiceToken, TokenServiceToken } from "../const.js";
13
- import { defineAbilitiesFor } from "./ability.js";
14
- import { AuthConfig } from "./auth.config.js";
15
- import { assertDevStaticAllowed, assertNonEmptyAudience } from "./auth.types.js";
16
- import { CaslAuthorizationService } from "./casl-authorization.service.js";
17
- import { TokenService } from "./token-service.js";
18
- export { ADMIN_ROLE, createPrincipalFromPayload, defineAbilitiesFor } from "./ability.js";
19
- export class BuilderExtensions {
20
- static withAuth(builder, abilityFactory = defineAbilitiesFor, options) {
21
- builder.registerPreBuildStep(async (configService, containerBuilder)=>{
22
- const authConfig = await configService.get(AuthConfig);
23
- const tokenConfig = toTokenServiceConfig(authConfig);
24
- const tokenService = new TokenService(tokenConfig, options?.principalMapper);
25
- containerBuilder.registerSingleton(TokenServiceToken, tokenService);
26
- containerBuilder.registerSingleton(AuthorizationServiceToken, new CaslAuthorizationService(abilityFactory, tokenService));
27
- });
28
- return builder;
29
- }
30
- }
31
- _ts_decorate([
32
- extension(Builder),
33
- _ts_metadata("design:type", Function),
34
- _ts_metadata("design:paramtypes", [
35
- typeof Builder === "undefined" ? Object : Builder,
36
- typeof AbilityFactory === "undefined" ? Object : AbilityFactory,
37
- typeof WithAuthOptions === "undefined" ? Object : WithAuthOptions
38
- ]),
39
- _ts_metadata("design:returntype", void 0)
40
- ], BuilderExtensions, "withAuth", null);
41
- function toTokenServiceConfig(config) {
42
- // Prefer real verification strategies over devStatic so a leftover
43
- // development block cannot disable OIDC/JWKS in any environment.
44
- if (config.discoveryUrl != null) {
45
- assertNonEmptyAudience(config.audience);
46
- return {
47
- discoveryUrl: config.discoveryUrl,
48
- audience: config.audience
49
- };
50
- }
51
- if (config.jwksUrl != null) {
52
- assertNonEmptyAudience(config.audience);
53
- return {
54
- jwksUrl: config.jwksUrl,
55
- issuer: config.issuer,
56
- audience: config.audience
57
- };
58
- }
59
- if (config.publicKey != null) {
60
- assertNonEmptyAudience(config.audience);
61
- return {
62
- publicKey: config.publicKey,
63
- algorithms: config.algorithms,
64
- issuer: config.issuer,
65
- audience: config.audience
66
- };
67
- }
68
- if (config.secret != null) {
69
- assertNonEmptyAudience(config.audience);
70
- return {
71
- secret: config.secret,
72
- issuer: config.issuer,
73
- audience: config.audience
74
- };
75
- }
76
- if (config.devStatic != null) {
77
- assertDevStaticAllowed();
78
- return {
79
- devStatic: config.devStatic
80
- };
81
- }
82
- throw new Error("AuthConfig must specify one of: discoveryUrl, jwksUrl, publicKey, secret, or devStatic");
83
- }
@@ -1,35 +0,0 @@
1
- export function isDiscoveryConfig(config) {
2
- return "discoveryUrl" in config;
3
- }
4
- export function isDevStaticConfig(config) {
5
- return "devStatic" in config;
6
- }
7
- export function isJwksConfig(config) {
8
- return "jwksUrl" in config;
9
- }
10
- export function isPublicKeyConfig(config) {
11
- return "publicKey" in config;
12
- }
13
- export function isSecretConfig(config) {
14
- return "secret" in config;
15
- }
16
- /** `devStatic` is only valid for local development NODE_ENV values. */ export function isDevelopmentNodeEnv(nodeEnv = process.env.NODE_ENV) {
17
- const env = nodeEnv?.toLowerCase();
18
- return env === "development" || env === "dev";
19
- }
20
- /**
21
- * Refuse `devStatic` outside development — otherwise any Bearer string is
22
- * accepted as the configured principal (auth bypass in production).
23
- */ export function assertDevStaticAllowed(nodeEnv = process.env.NODE_ENV) {
24
- if (!isDevelopmentNodeEnv(nodeEnv)) {
25
- throw new Error(`AuthConfig.devStatic is only allowed when NODE_ENV is "development" or "dev" (got ${JSON.stringify(nodeEnv ?? "")}).`);
26
- }
27
- }
28
- /**
29
- * jose skips `aud` validation when `audience` is undefined or "". Require a
30
- * non-empty audience for every non-devStatic verification strategy.
31
- */ export function assertNonEmptyAudience(audience) {
32
- if (typeof audience !== "string" || audience.trim().length === 0) {
33
- throw new Error(`JWT audience must be a non-empty string (got ${JSON.stringify(audience)}). ` + "Empty audience disables aud validation and accepts tokens minted for other APIs.");
34
- }
35
- }
@@ -1,40 +0,0 @@
1
- import { ForbiddenError } from "../routing/anvil.types.js";
2
- /**
3
- * IAuthorizationService implementation backed by CASL.
4
- *
5
- * Register it under AuthorizationServiceToken:
6
- * ```ts
7
- * builder.withServices((c) =>
8
- * c.registerSingleton(AuthorizationServiceToken, new CaslAuthorizationService((principal) => {
9
- * const { can, build } = new AbilityBuilder(createMongoAbility);
10
- * if (principal.roles?.includes("admin")) can("manage", "all");
11
- * else can("read", "Post");
12
- * return build();
13
- * }))
14
- * );
15
- * ```
16
- */ export class CaslAuthorizationService {
17
- factory;
18
- tokenService;
19
- constructor(factory, tokenService){
20
- this.factory = factory;
21
- this.tokenService = tokenService;
22
- }
23
- async authorizeRequest(request, action, subject) {
24
- const principal = await this.authenticateRequest(request);
25
- await this.authorize(principal, action, subject);
26
- return principal;
27
- }
28
- async authenticateRequest(request) {
29
- if (!this.tokenService) {
30
- throw new Error("CaslAuthorizationService requires a TokenService to authenticate requests. Register it via .withAuth().");
31
- }
32
- return this.tokenService.verifyRequest(request);
33
- }
34
- async authorize(principal, action, subject) {
35
- const ability = this.factory(principal);
36
- if (ability.cannot(action, subject)) {
37
- throw new ForbiddenError(`Cannot perform "${action}" on "${subject}"`);
38
- }
39
- }
40
- }
@@ -1,6 +0,0 @@
1
- export * from "./ability.js";
2
- export * from "./auth.config.js";
3
- export * from "./auth.types.js";
4
- export * from "./casl-authorization.service.js";
5
- export * from "./principal-provider.js";
6
- export * from "./token-service.js";
@@ -1,14 +0,0 @@
1
- /**
2
- * Concrete, request-scoped implementation. The Router sets the principal
3
- * after JWT verification; downstream services consume it via IPrincipalProvider.
4
- *
5
- * @internal — framework use only. Inject IPrincipalProvider, not this class.
6
- */ export class PrincipalProvider {
7
- _principal;
8
- get currentPrincipal() {
9
- return this._principal;
10
- }
11
- setPrincipal(principal) {
12
- this._principal = principal;
13
- }
14
- }
@@ -1,99 +0,0 @@
1
- import { lookup } from "node:dns/promises";
2
- import { BlockList, isIP } from "node:net";
3
- /**
4
- * Validate URLs used for OIDC discovery / JWKS fetches to reduce SSRF risk.
5
- *
6
- * - http(s) only; https required except loopback (local IdP tests)
7
- * - no credentials in the URL
8
- * - hostname must not resolve to private, link-local, or metadata ranges
9
- * (loopback allowed only when NODE_ENV is development/dev/test)
10
- */ export async function assertSafeOutboundUrl(raw, label) {
11
- let url;
12
- try {
13
- url = new URL(raw);
14
- } catch {
15
- throw new Error(`Invalid ${label}: ${raw}`);
16
- }
17
- if (url.protocol !== "https:" && url.protocol !== "http:") {
18
- throw new Error(`${label} must use http or https (got ${url.protocol})`);
19
- }
20
- if (url.username || url.password) {
21
- throw new Error(`${label} must not include credentials`);
22
- }
23
- const hostname = url.hostname.replace(/^\[|\]$/g, "");
24
- if (isBlockedHostname(hostname)) {
25
- throw new Error(`${label} host is not allowed (${hostname})`);
26
- }
27
- const ips = await resolveIps(hostname);
28
- const allowLoopback = isLoopbackAllowed();
29
- for (const ip of ips){
30
- if (isBlockedIp(ip, allowLoopback)) {
31
- throw new Error(`${label} resolves to a blocked address (${ip})`);
32
- }
33
- }
34
- if (url.protocol === "http:") {
35
- const loopbackOnly = isLoopbackHostname(hostname) || ips.length > 0 && ips.every((ip)=>isLoopbackIp(ip));
36
- if (!loopbackOnly || !allowLoopback) {
37
- throw new Error(`${label} must use https unless targeting loopback in development/test`);
38
- }
39
- }
40
- return url;
41
- }
42
- function isLoopbackAllowed() {
43
- const env = process.env.NODE_ENV ?? "";
44
- return env === "development" || env === "dev" || env === "test";
45
- }
46
- function isLoopbackHostname(hostname) {
47
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
48
- }
49
- function isBlockedHostname(hostname) {
50
- const lower = hostname.toLowerCase();
51
- return lower === "metadata.google.internal" || lower === "metadata" || lower.endsWith(".metadata.google.internal");
52
- }
53
- async function resolveIps(hostname) {
54
- if (isIP(hostname)) {
55
- return [
56
- hostname
57
- ];
58
- }
59
- const results = await lookup(hostname, {
60
- all: true,
61
- verbatim: true
62
- });
63
- return results.map((r)=>r.address);
64
- }
65
- function isLoopbackIp(ip) {
66
- const list = new BlockList();
67
- list.addSubnet("127.0.0.0", 8, "ipv4");
68
- list.addAddress("::1", "ipv6");
69
- list.addSubnet("::ffff:127.0.0.0", 104, "ipv6");
70
- return list.check(ip);
71
- }
72
- function isBlockedIp(ip, allowLoopback) {
73
- if (allowLoopback && isLoopbackIp(ip)) {
74
- return false;
75
- }
76
- const list = new BlockList();
77
- // IPv4 special-use / private
78
- list.addSubnet("0.0.0.0", 8, "ipv4");
79
- list.addSubnet("10.0.0.0", 8, "ipv4");
80
- list.addSubnet("100.64.0.0", 10, "ipv4"); // shared address space
81
- list.addSubnet("127.0.0.0", 8, "ipv4");
82
- list.addSubnet("169.254.0.0", 16, "ipv4"); // link-local / cloud metadata
83
- list.addSubnet("172.16.0.0", 12, "ipv4");
84
- list.addSubnet("192.168.0.0", 16, "ipv4");
85
- list.addSubnet("198.18.0.0", 15, "ipv4"); // benchmarking
86
- // IPv6
87
- list.addAddress("::1", "ipv6");
88
- list.addSubnet("fc00::", 7, "ipv6"); // unique local
89
- list.addSubnet("fe80::", 10, "ipv6"); // link-local
90
- if (list.check(ip)) {
91
- return true;
92
- }
93
- // Mapped IPv4 inside IPv6 (::ffff:a.b.c.d)
94
- const mapped = ip.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
95
- if (mapped) {
96
- return isBlockedIp(mapped[1], allowLoopback);
97
- }
98
- return false;
99
- }