@openstax/ts-utils 1.33.0 → 1.34.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 (503) hide show
  1. package/.cfnlintrc +2 -0
  2. package/.github/CODEOWNERS +1 -0
  3. package/.github/workflows/ci.yml +36 -0
  4. package/.github/workflows/lint.yml +55 -0
  5. package/.nvmrc +1 -0
  6. package/.syncignore +4 -0
  7. package/.syncpackrc +18 -0
  8. package/CONTRIBUTING.md +96 -0
  9. package/LICENSE +661 -0
  10. package/Procfile +1 -0
  11. package/README.md +62 -90
  12. package/app.json +23 -0
  13. package/cspell.json +32 -0
  14. package/deploy/constants.env +21 -0
  15. package/deploy/deploy.bash +157 -0
  16. package/deploy/deployment-alt-region.cfn.yml +70 -0
  17. package/deploy/deployment.cfn.yml +650 -0
  18. package/deploy/destroy-deployment.bash +23 -0
  19. package/deploy/shared.cfn.yml +94 -0
  20. package/docs/lambda-build.md +35 -0
  21. package/package.json +12 -228
  22. package/packages/frontend/README.md +46 -0
  23. package/packages/frontend/package.json +101 -0
  24. package/packages/frontend/public/favicon.ico +0 -0
  25. package/packages/frontend/public/index.html +107 -0
  26. package/packages/frontend/public/maintenance.html +59 -0
  27. package/packages/frontend/public/manifest.json +15 -0
  28. package/packages/frontend/public/robots.txt +3 -0
  29. package/packages/frontend/script/make-certificate.bash +49 -0
  30. package/packages/frontend/script/server/cli.js +11 -0
  31. package/packages/frontend/script/server/index.js +47 -0
  32. package/packages/frontend/script/start.bash +22 -0
  33. package/packages/frontend/script/trust-localhost.bash +7 -0
  34. package/packages/frontend/src/auth/authProvider.ts +10 -0
  35. package/packages/frontend/src/auth/useAuth.ts +33 -0
  36. package/packages/frontend/src/components/Pagination.tsx +26 -0
  37. package/packages/frontend/src/configProvider/index.ts +53 -0
  38. package/packages/frontend/src/configProvider/use.ts +41 -0
  39. package/packages/frontend/src/core/context/services.spec.tsx +39 -0
  40. package/packages/frontend/src/core/context/services.tsx +16 -0
  41. package/packages/frontend/src/core/index.spec.ts +7 -0
  42. package/packages/frontend/src/core/index.ts +20 -0
  43. package/packages/frontend/src/core/services.tsx +14 -0
  44. package/packages/frontend/src/core/types.ts +3 -0
  45. package/packages/frontend/src/example/api.ts +28 -0
  46. package/packages/frontend/src/example/components/Layout.tsx +23 -0
  47. package/packages/frontend/src/example/screens/Home.spec.tsx +68 -0
  48. package/packages/frontend/src/example/screens/Home.tsx +78 -0
  49. package/packages/frontend/src/example/screens/ThingList.spec.tsx +60 -0
  50. package/packages/frontend/src/example/screens/ThingList.tsx +75 -0
  51. package/packages/frontend/src/example/screens/ThingView.spec.tsx +71 -0
  52. package/packages/frontend/src/example/screens/ThingView.tsx +47 -0
  53. package/packages/frontend/src/example/screens/index.ts +9 -0
  54. package/packages/frontend/src/index.css +159 -0
  55. package/packages/frontend/src/index.tsx +67 -0
  56. package/packages/frontend/src/react-app-env.d.ts +1 -0
  57. package/packages/frontend/src/routing/components/RouteLink.spec.tsx +55 -0
  58. package/packages/frontend/src/routing/components/RouteLink.tsx +35 -0
  59. package/packages/frontend/src/routing/middleware.ts +6 -0
  60. package/packages/frontend/src/routing/useQuery.ts +14 -0
  61. package/packages/frontend/src/setupProxy.js +19 -0
  62. package/packages/frontend/src/setupTests.ts +9 -0
  63. package/packages/frontend/src/tests/testServices.tsx +23 -0
  64. package/packages/frontend/tsconfig.json +27 -0
  65. package/packages/lambda/.eslintrc.js +64 -0
  66. package/packages/lambda/jest-global-setup.js +3 -0
  67. package/packages/lambda/jest-setup-after-env.js +1 -0
  68. package/packages/lambda/jest.config.js +31 -0
  69. package/packages/lambda/jest.resolver.js +17 -0
  70. package/packages/lambda/package.json +68 -0
  71. package/packages/lambda/script/build.bash +19 -0
  72. package/packages/lambda/script/bundle-functions.bash +10 -0
  73. package/packages/lambda/script/lambdaLocalProxy.js +16 -0
  74. package/packages/lambda/script/lambdaLocalProxy.spec.ts +147 -0
  75. package/packages/lambda/script/utils/getRouteData.ts +7 -0
  76. package/packages/lambda/script/utils/routeDataLoader.js +8 -0
  77. package/packages/lambda/script/utils/routeDataLoader.spec.ts +8 -0
  78. package/packages/lambda/src/functions/serviceApi/core/index.ts +7 -0
  79. package/packages/lambda/src/functions/serviceApi/core/request.spec.ts +38 -0
  80. package/packages/lambda/src/functions/serviceApi/core/request.ts +42 -0
  81. package/packages/lambda/src/functions/serviceApi/core/routes.spec.ts +7 -0
  82. package/packages/lambda/src/functions/serviceApi/core/routes.ts +10 -0
  83. package/packages/lambda/src/functions/serviceApi/core/services.ts +9 -0
  84. package/packages/lambda/src/functions/serviceApi/core/types.ts +13 -0
  85. package/packages/lambda/src/functions/serviceApi/entry/lambda/https-xray.ts +4 -0
  86. package/packages/lambda/src/functions/serviceApi/entry/lambda/index.spec.ts +48 -0
  87. package/packages/lambda/src/functions/serviceApi/entry/lambda/index.ts +58 -0
  88. package/packages/lambda/src/functions/serviceApi/entry/lambda/services.ts +36 -0
  89. package/packages/lambda/src/functions/serviceApi/entry/local.ts +71 -0
  90. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentSearchMiddleware.spec.ts +16 -0
  91. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentSearchMiddleware.ts +41 -0
  92. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentStoreMiddleware.spec.ts +78 -0
  93. package/packages/lambda/src/functions/serviceApi/versions/v0/example/documentStoreMiddleware.ts +70 -0
  94. package/packages/lambda/src/functions/serviceApi/versions/v0/example/routes.spec.ts +306 -0
  95. package/packages/lambda/src/functions/serviceApi/versions/v0/example/routes.ts +176 -0
  96. package/packages/lambda/src/functions/serviceApi/versions/v0/index.spec.ts +263 -0
  97. package/packages/lambda/src/functions/serviceApi/versions/v0/index.ts +134 -0
  98. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/authMiddleware.spec.ts +23 -0
  99. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/authMiddleware.ts +32 -0
  100. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/configMiddleware.spec.ts +10 -0
  101. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/configMiddleware.ts +7 -0
  102. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/frontendFileServerMiddleware.spec.ts +13 -0
  103. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/frontendFileServerMiddleware.ts +23 -0
  104. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/paginationMiddleware.spec.ts +9 -0
  105. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/paginationMiddleware.ts +9 -0
  106. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/searchMiddleware.spec.ts +12 -0
  107. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/searchMiddleware.ts +21 -0
  108. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/userRoleValidatorMiddleware.spec.ts +21 -0
  109. package/packages/lambda/src/functions/serviceApi/versions/v0/middleware/userRoleValidatorMiddleware.ts +18 -0
  110. package/packages/lambda/tsconfig.json +30 -0
  111. package/packages/lambda/webpack.config.js +97 -0
  112. package/packages/utils/.eslintrc.js +64 -0
  113. package/packages/utils/README.md +118 -0
  114. package/packages/utils/jest-global-setup.js +3 -0
  115. package/packages/utils/jest.config.js +25 -0
  116. package/packages/utils/jest.resolver.js +17 -0
  117. package/packages/utils/package.json +238 -0
  118. package/packages/utils/src/assertions/index.spec.ts +126 -0
  119. package/{dist/esm/assertions/index.js → packages/utils/src/assertions/index.ts} +64 -49
  120. package/packages/utils/src/aws/ssmService.ts +7 -0
  121. package/packages/utils/src/config/awsParameterConfig.ts +24 -0
  122. package/packages/utils/src/config/envConfig.ts +58 -0
  123. package/packages/utils/src/config/index.spec.ts +165 -0
  124. package/{dist/esm/config/index.d.ts → packages/utils/src/config/index.ts} +29 -13
  125. package/packages/utils/src/config/lambdaParameterConfig.ts +49 -0
  126. package/{dist/esm/config/replaceConfig.js → packages/utils/src/config/replaceConfig.ts} +16 -6
  127. package/packages/utils/src/config/resolveConfigValue.ts +10 -0
  128. package/packages/utils/src/errors/index.spec.ts +35 -0
  129. package/{dist/esm/errors/index.js → packages/utils/src/errors/index.ts} +57 -41
  130. package/packages/utils/src/fetch/fetchStatusRetry.spec.ts +197 -0
  131. package/packages/utils/src/fetch/fetchStatusRetry.ts +33 -0
  132. package/packages/utils/src/fetch/index.spec.ts +34 -0
  133. package/packages/utils/src/fetch/index.ts +87 -0
  134. package/packages/utils/src/guards/index.spec.ts +58 -0
  135. package/{dist/esm/guards/index.d.ts → packages/utils/src/guards/index.ts} +10 -7
  136. package/packages/utils/src/index.spec.ts +471 -0
  137. package/packages/utils/src/middleware/apiErrorHandler.spec.ts +65 -0
  138. package/packages/utils/src/middleware/apiErrorHandler.ts +67 -0
  139. package/packages/utils/src/middleware/apiSlowResponseMiddleware.spec.ts +184 -0
  140. package/packages/utils/src/middleware/apiSlowResponseMiddleware.ts +71 -0
  141. package/packages/utils/src/middleware/index.spec.ts +99 -0
  142. package/{dist/cjs/middleware/index.d.ts → packages/utils/src/middleware/index.ts} +53 -5
  143. package/packages/utils/src/middleware/lambdaCorsResponseMiddleware.spec.ts +103 -0
  144. package/packages/utils/src/middleware/lambdaCorsResponseMiddleware.ts +52 -0
  145. package/packages/utils/src/middleware/throwNotFoundMiddleware.spec.ts +20 -0
  146. package/packages/utils/src/middleware/throwNotFoundMiddleware.ts +11 -0
  147. package/packages/utils/src/misc/hashValue.ts +18 -0
  148. package/packages/utils/src/misc/helpers.ts +259 -0
  149. package/packages/utils/src/misc/merge.ts +48 -0
  150. package/{dist/esm/misc/partitionSequence.js → packages/utils/src/misc/partitionSequence.ts} +23 -15
  151. package/packages/utils/src/pagination/index.spec.ts +150 -0
  152. package/packages/utils/src/pagination/index.ts +117 -0
  153. package/{dist/esm/routing/helpers.js → packages/utils/src/routing/helpers.ts} +42 -30
  154. package/packages/utils/src/routing/index.spec.ts +553 -0
  155. package/packages/utils/src/routing/index.ts +424 -0
  156. package/packages/utils/src/routing/validators/zod.spec.ts +16 -0
  157. package/packages/utils/src/routing/validators/zod.ts +14 -0
  158. package/packages/utils/src/services/accountsGateway/README.md +3 -0
  159. package/packages/utils/src/services/accountsGateway/index.spec.ts +518 -0
  160. package/packages/utils/src/services/accountsGateway/index.ts +251 -0
  161. package/packages/utils/src/services/apiGateway/README.md +93 -0
  162. package/packages/utils/src/services/apiGateway/index.spec.ts +254 -0
  163. package/packages/utils/src/services/apiGateway/index.ts +189 -0
  164. package/packages/utils/src/services/authProvider/README.md +21 -0
  165. package/packages/utils/src/services/authProvider/browser.spec.ts +391 -0
  166. package/packages/utils/src/services/authProvider/browser.ts +209 -0
  167. package/packages/utils/src/services/authProvider/decryption.spec.ts +337 -0
  168. package/packages/utils/src/services/authProvider/decryption.ts +98 -0
  169. package/packages/utils/src/services/authProvider/index.ts +93 -0
  170. package/packages/utils/src/services/authProvider/stub.spec.ts +29 -0
  171. package/packages/utils/src/services/authProvider/subrequest.spec.ts +105 -0
  172. package/packages/utils/src/services/authProvider/subrequest.ts +68 -0
  173. package/packages/utils/src/services/authProvider/utils/decryptAndVerify.spec.ts +128 -0
  174. package/packages/utils/src/services/authProvider/utils/decryptAndVerify.ts +106 -0
  175. package/packages/utils/src/services/authProvider/utils/embeddedAuthProvider.spec.ts +26 -0
  176. package/packages/utils/src/services/authProvider/utils/embeddedAuthProvider.ts +57 -0
  177. package/packages/utils/src/services/authProvider/utils/userRoleValidator.spec.ts +135 -0
  178. package/packages/utils/src/services/authProvider/utils/userRoleValidator.ts +49 -0
  179. package/packages/utils/src/services/authProvider/utils/userSubrequest.spec.ts +26 -0
  180. package/packages/utils/src/services/authProvider/utils/userSubrequest.ts +10 -0
  181. package/packages/utils/src/services/documentStore/dynamoEncoding.ts +57 -0
  182. package/packages/utils/src/services/documentStore/fileSystemAssert.spec.ts +43 -0
  183. package/packages/utils/src/services/documentStore/fileSystemAssert.ts +10 -0
  184. package/{dist/cjs/services/documentStore/index.d.ts → packages/utils/src/services/documentStore/index.ts} +8 -8
  185. package/packages/utils/src/services/documentStore/unversioned/README.md +13 -0
  186. package/packages/utils/src/services/documentStore/unversioned/dynamodb.spec.ts +859 -0
  187. package/packages/utils/src/services/documentStore/unversioned/dynamodb.ts +243 -0
  188. package/packages/utils/src/services/documentStore/unversioned/file-system.spec.ts +629 -0
  189. package/packages/utils/src/services/documentStore/unversioned/file-system.ts +194 -0
  190. package/{dist/cjs/services/documentStore/unversioned/index.d.ts → packages/utils/src/services/documentStore/unversioned/index.ts} +2 -0
  191. package/packages/utils/src/services/documentStore/versioned/README.md +13 -0
  192. package/packages/utils/src/services/documentStore/versioned/dynamodb.spec.ts +376 -0
  193. package/packages/utils/src/services/documentStore/versioned/dynamodb.ts +167 -0
  194. package/packages/utils/src/services/documentStore/versioned/file-system.spec.ts +262 -0
  195. package/packages/utils/src/services/documentStore/versioned/file-system.ts +90 -0
  196. package/packages/utils/src/services/documentStore/versioned/index.ts +25 -0
  197. package/packages/utils/src/services/exercisesGateway/README.md +5 -0
  198. package/packages/utils/src/services/exercisesGateway/index.spec.ts +326 -0
  199. package/packages/utils/src/services/exercisesGateway/index.ts +163 -0
  200. package/packages/utils/src/services/fileServer/index.spec.ts +88 -0
  201. package/packages/utils/src/services/fileServer/index.ts +43 -0
  202. package/packages/utils/src/services/fileServer/localFileServer.spec.ts +182 -0
  203. package/packages/utils/src/services/fileServer/localFileServer.ts +159 -0
  204. package/packages/utils/src/services/fileServer/s3FileServer.spec.ts +266 -0
  205. package/packages/utils/src/services/fileServer/s3FileServer.ts +155 -0
  206. package/packages/utils/src/services/launchParams/index.spec.ts +366 -0
  207. package/packages/utils/src/services/launchParams/signer.ts +73 -0
  208. package/packages/utils/src/services/launchParams/verifier.ts +120 -0
  209. package/packages/utils/src/services/logger/console.spec.ts +29 -0
  210. package/{dist/esm/services/logger/console.js → packages/utils/src/services/logger/console.ts} +5 -2
  211. package/packages/utils/src/services/logger/index.spec.ts +65 -0
  212. package/{dist/esm/services/logger/index.d.ts → packages/utils/src/services/logger/index.ts} +23 -9
  213. package/packages/utils/src/services/lrsGateway/README.md +5 -0
  214. package/packages/utils/src/services/lrsGateway/addStatementDefaultFields.ts +22 -0
  215. package/packages/utils/src/services/lrsGateway/attempt-utils.spec.ts +847 -0
  216. package/packages/utils/src/services/lrsGateway/attempt-utils.ts +358 -0
  217. package/packages/utils/src/services/lrsGateway/file-system.spec.ts +363 -0
  218. package/packages/utils/src/services/lrsGateway/file-system.ts +165 -0
  219. package/packages/utils/src/services/lrsGateway/index.spec.ts +194 -0
  220. package/packages/utils/src/services/lrsGateway/index.ts +257 -0
  221. package/packages/utils/src/services/lrsGateway/xapiUtils.spec.ts +887 -0
  222. package/packages/utils/src/services/lrsGateway/xapiUtils.ts +262 -0
  223. package/packages/utils/src/services/postgresConnection/index.spec.ts +170 -0
  224. package/packages/utils/src/services/postgresConnection/index.ts +84 -0
  225. package/packages/utils/src/services/searchProvider/README.md +3 -0
  226. package/packages/utils/src/services/searchProvider/index.ts +59 -0
  227. package/packages/utils/src/services/searchProvider/memorySearchTheBadWay.spec.ts +526 -0
  228. package/packages/utils/src/services/searchProvider/memorySearchTheBadWay.ts +223 -0
  229. package/packages/utils/src/services/searchProvider/openSearch.spec.ts +926 -0
  230. package/packages/utils/src/services/searchProvider/openSearch.ts +195 -0
  231. package/{dist/esm/types.d.ts → packages/utils/src/types.ts} +34 -6
  232. package/packages/utils/tsconfig.json +31 -0
  233. package/packages/utils/tsconfig.without-specs.cjs.json +7 -0
  234. package/packages/utils/tsconfig.without-specs.esm.json +7 -0
  235. package/packages/utils/tsconfig.without-specs.json +6 -0
  236. package/scripts/build.bash +24 -0
  237. package/scripts/ci.bash +10 -0
  238. package/scripts/start.bash +29 -0
  239. package/dist/cjs/assertions/index.d.ts +0 -89
  240. package/dist/cjs/assertions/index.js +0 -157
  241. package/dist/cjs/aws/ssmService.d.ts +0 -5
  242. package/dist/cjs/aws/ssmService.js +0 -9
  243. package/dist/cjs/config/awsParameterConfig.d.ts +0 -10
  244. package/dist/cjs/config/awsParameterConfig.js +0 -26
  245. package/dist/cjs/config/envConfig.d.ts +0 -24
  246. package/dist/cjs/config/envConfig.js +0 -57
  247. package/dist/cjs/config/index.d.ts +0 -48
  248. package/dist/cjs/config/index.js +0 -35
  249. package/dist/cjs/config/lambdaParameterConfig.d.ts +0 -12
  250. package/dist/cjs/config/lambdaParameterConfig.js +0 -45
  251. package/dist/cjs/config/replaceConfig.d.ts +0 -14
  252. package/dist/cjs/config/replaceConfig.js +0 -22
  253. package/dist/cjs/config/resolveConfigValue.d.ts +0 -5
  254. package/dist/cjs/config/resolveConfigValue.js +0 -12
  255. package/dist/cjs/errors/index.d.ts +0 -88
  256. package/dist/cjs/errors/index.js +0 -123
  257. package/dist/cjs/fetch/fetchStatusRetry.d.ts +0 -8
  258. package/dist/cjs/fetch/fetchStatusRetry.js +0 -27
  259. package/dist/cjs/fetch/index.d.ts +0 -64
  260. package/dist/cjs/fetch/index.js +0 -55
  261. package/dist/cjs/guards/index.d.ts +0 -38
  262. package/dist/cjs/guards/index.js +0 -44
  263. package/dist/cjs/index.js +0 -20
  264. package/dist/cjs/middleware/apiErrorHandler.d.ts +0 -24
  265. package/dist/cjs/middleware/apiErrorHandler.js +0 -42
  266. package/dist/cjs/middleware/apiSlowResponseMiddleware.d.ts +0 -23
  267. package/dist/cjs/middleware/apiSlowResponseMiddleware.js +0 -54
  268. package/dist/cjs/middleware/index.js +0 -48
  269. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.d.ts +0 -20
  270. package/dist/cjs/middleware/lambdaCorsResponseMiddleware.js +0 -44
  271. package/dist/cjs/middleware/throwNotFoundMiddleware.d.ts +0 -4
  272. package/dist/cjs/middleware/throwNotFoundMiddleware.js +0 -14
  273. package/dist/cjs/misc/hashValue.d.ts +0 -10
  274. package/dist/cjs/misc/hashValue.js +0 -17
  275. package/dist/cjs/misc/helpers.d.ts +0 -124
  276. package/dist/cjs/misc/helpers.js +0 -214
  277. package/dist/cjs/misc/merge.d.ts +0 -21
  278. package/dist/cjs/misc/merge.js +0 -45
  279. package/dist/cjs/misc/partitionSequence.d.ts +0 -35
  280. package/dist/cjs/misc/partitionSequence.js +0 -55
  281. package/dist/cjs/pagination/index.d.ts +0 -91
  282. package/dist/cjs/pagination/index.js +0 -83
  283. package/dist/cjs/routing/helpers.d.ts +0 -57
  284. package/dist/cjs/routing/helpers.js +0 -90
  285. package/dist/cjs/routing/index.d.ts +0 -290
  286. package/dist/cjs/routing/index.js +0 -295
  287. package/dist/cjs/routing/validators/zod.d.ts +0 -4
  288. package/dist/cjs/routing/validators/zod.js +0 -14
  289. package/dist/cjs/services/accountsGateway/index.d.ts +0 -92
  290. package/dist/cjs/services/accountsGateway/index.js +0 -138
  291. package/dist/cjs/services/apiGateway/index.d.ts +0 -68
  292. package/dist/cjs/services/apiGateway/index.js +0 -118
  293. package/dist/cjs/services/authProvider/browser.d.ts +0 -40
  294. package/dist/cjs/services/authProvider/browser.js +0 -155
  295. package/dist/cjs/services/authProvider/decryption.d.ts +0 -19
  296. package/dist/cjs/services/authProvider/decryption.js +0 -73
  297. package/dist/cjs/services/authProvider/index.d.ts +0 -63
  298. package/dist/cjs/services/authProvider/index.js +0 -34
  299. package/dist/cjs/services/authProvider/subrequest.d.ts +0 -13
  300. package/dist/cjs/services/authProvider/subrequest.js +0 -49
  301. package/dist/cjs/services/authProvider/utils/decryptAndVerify.d.ts +0 -28
  302. package/dist/cjs/services/authProvider/utils/decryptAndVerify.js +0 -91
  303. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.d.ts +0 -26
  304. package/dist/cjs/services/authProvider/utils/embeddedAuthProvider.js +0 -47
  305. package/dist/cjs/services/authProvider/utils/userRoleValidator.d.ts +0 -13
  306. package/dist/cjs/services/authProvider/utils/userRoleValidator.js +0 -37
  307. package/dist/cjs/services/authProvider/utils/userSubrequest.d.ts +0 -3
  308. package/dist/cjs/services/authProvider/utils/userSubrequest.js +0 -13
  309. package/dist/cjs/services/documentStore/dynamoEncoding.d.ts +0 -10
  310. package/dist/cjs/services/documentStore/dynamoEncoding.js +0 -52
  311. package/dist/cjs/services/documentStore/fileSystemAssert.d.ts +0 -1
  312. package/dist/cjs/services/documentStore/fileSystemAssert.js +0 -14
  313. package/dist/cjs/services/documentStore/index.js +0 -2
  314. package/dist/cjs/services/documentStore/unversioned/dynamodb.d.ts +0 -31
  315. package/dist/cjs/services/documentStore/unversioned/dynamodb.js +0 -233
  316. package/dist/cjs/services/documentStore/unversioned/file-system.d.ts +0 -32
  317. package/dist/cjs/services/documentStore/unversioned/file-system.js +0 -214
  318. package/dist/cjs/services/documentStore/unversioned/index.js +0 -2
  319. package/dist/cjs/services/documentStore/versioned/dynamodb.d.ts +0 -25
  320. package/dist/cjs/services/documentStore/versioned/dynamodb.js +0 -143
  321. package/dist/cjs/services/documentStore/versioned/file-system.d.ts +0 -25
  322. package/dist/cjs/services/documentStore/versioned/file-system.js +0 -73
  323. package/dist/cjs/services/documentStore/versioned/index.d.ts +0 -17
  324. package/dist/cjs/services/documentStore/versioned/index.js +0 -2
  325. package/dist/cjs/services/exercisesGateway/index.d.ts +0 -67
  326. package/dist/cjs/services/exercisesGateway/index.js +0 -107
  327. package/dist/cjs/services/fileServer/index.d.ts +0 -30
  328. package/dist/cjs/services/fileServer/index.js +0 -19
  329. package/dist/cjs/services/fileServer/localFileServer.d.ts +0 -13
  330. package/dist/cjs/services/fileServer/localFileServer.js +0 -132
  331. package/dist/cjs/services/fileServer/s3FileServer.d.ts +0 -14
  332. package/dist/cjs/services/fileServer/s3FileServer.js +0 -131
  333. package/dist/cjs/services/launchParams/index.js +0 -7
  334. package/dist/cjs/services/launchParams/signer.d.ts +0 -23
  335. package/dist/cjs/services/launchParams/signer.js +0 -58
  336. package/dist/cjs/services/launchParams/verifier.d.ts +0 -21
  337. package/dist/cjs/services/launchParams/verifier.js +0 -129
  338. package/dist/cjs/services/logger/console.d.ts +0 -4
  339. package/dist/cjs/services/logger/console.js +0 -12
  340. package/dist/cjs/services/logger/index.d.ts +0 -39
  341. package/dist/cjs/services/logger/index.js +0 -31
  342. package/dist/cjs/services/lrsGateway/addStatementDefaultFields.d.ts +0 -5
  343. package/dist/cjs/services/lrsGateway/addStatementDefaultFields.js +0 -21
  344. package/dist/cjs/services/lrsGateway/attempt-utils.d.ts +0 -70
  345. package/dist/cjs/services/lrsGateway/attempt-utils.js +0 -258
  346. package/dist/cjs/services/lrsGateway/file-system.d.ts +0 -15
  347. package/dist/cjs/services/lrsGateway/file-system.js +0 -150
  348. package/dist/cjs/services/lrsGateway/index.d.ts +0 -122
  349. package/dist/cjs/services/lrsGateway/index.js +0 -148
  350. package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +0 -68
  351. package/dist/cjs/services/lrsGateway/xapiUtils.js +0 -109
  352. package/dist/cjs/services/postgresConnection/index.d.ts +0 -28
  353. package/dist/cjs/services/postgresConnection/index.js +0 -65
  354. package/dist/cjs/services/searchProvider/index.d.ts +0 -67
  355. package/dist/cjs/services/searchProvider/index.js +0 -2
  356. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.d.ts +0 -20
  357. package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +0 -191
  358. package/dist/cjs/services/searchProvider/openSearch.d.ts +0 -28
  359. package/dist/cjs/services/searchProvider/openSearch.js +0 -154
  360. package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +0 -1
  361. package/dist/cjs/types.d.ts +0 -31
  362. package/dist/cjs/types.js +0 -2
  363. package/dist/esm/assertions/index.d.ts +0 -89
  364. package/dist/esm/aws/ssmService.d.ts +0 -5
  365. package/dist/esm/aws/ssmService.js +0 -6
  366. package/dist/esm/config/awsParameterConfig.d.ts +0 -10
  367. package/dist/esm/config/awsParameterConfig.js +0 -22
  368. package/dist/esm/config/envConfig.d.ts +0 -24
  369. package/dist/esm/config/envConfig.js +0 -53
  370. package/dist/esm/config/index.js +0 -17
  371. package/dist/esm/config/lambdaParameterConfig.d.ts +0 -12
  372. package/dist/esm/config/lambdaParameterConfig.js +0 -38
  373. package/dist/esm/config/replaceConfig.d.ts +0 -14
  374. package/dist/esm/config/resolveConfigValue.d.ts +0 -5
  375. package/dist/esm/config/resolveConfigValue.js +0 -8
  376. package/dist/esm/errors/index.d.ts +0 -88
  377. package/dist/esm/fetch/fetchStatusRetry.d.ts +0 -8
  378. package/dist/esm/fetch/fetchStatusRetry.js +0 -23
  379. package/dist/esm/fetch/index.d.ts +0 -64
  380. package/dist/esm/fetch/index.js +0 -46
  381. package/dist/esm/guards/index.js +0 -36
  382. package/dist/esm/index.d.ts +0 -4
  383. package/dist/esm/index.js +0 -4
  384. package/dist/esm/middleware/apiErrorHandler.d.ts +0 -24
  385. package/dist/esm/middleware/apiErrorHandler.js +0 -38
  386. package/dist/esm/middleware/apiSlowResponseMiddleware.d.ts +0 -23
  387. package/dist/esm/middleware/apiSlowResponseMiddleware.js +0 -50
  388. package/dist/esm/middleware/index.d.ts +0 -47
  389. package/dist/esm/middleware/index.js +0 -44
  390. package/dist/esm/middleware/lambdaCorsResponseMiddleware.d.ts +0 -20
  391. package/dist/esm/middleware/lambdaCorsResponseMiddleware.js +0 -40
  392. package/dist/esm/middleware/throwNotFoundMiddleware.d.ts +0 -4
  393. package/dist/esm/middleware/throwNotFoundMiddleware.js +0 -10
  394. package/dist/esm/misc/hashValue.d.ts +0 -10
  395. package/dist/esm/misc/hashValue.js +0 -13
  396. package/dist/esm/misc/helpers.d.ts +0 -124
  397. package/dist/esm/misc/helpers.js +0 -199
  398. package/dist/esm/misc/merge.d.ts +0 -21
  399. package/dist/esm/misc/merge.js +0 -40
  400. package/dist/esm/misc/partitionSequence.d.ts +0 -35
  401. package/dist/esm/pagination/index.d.ts +0 -91
  402. package/dist/esm/pagination/index.js +0 -77
  403. package/dist/esm/routing/helpers.d.ts +0 -57
  404. package/dist/esm/routing/index.d.ts +0 -290
  405. package/dist/esm/routing/index.js +0 -246
  406. package/dist/esm/routing/validators/zod.d.ts +0 -4
  407. package/dist/esm/routing/validators/zod.js +0 -10
  408. package/dist/esm/services/accountsGateway/index.d.ts +0 -92
  409. package/dist/esm/services/accountsGateway/index.js +0 -131
  410. package/dist/esm/services/apiGateway/index.d.ts +0 -68
  411. package/dist/esm/services/apiGateway/index.js +0 -77
  412. package/dist/esm/services/authProvider/browser.d.ts +0 -40
  413. package/dist/esm/services/authProvider/browser.js +0 -151
  414. package/dist/esm/services/authProvider/decryption.d.ts +0 -19
  415. package/dist/esm/services/authProvider/decryption.js +0 -69
  416. package/dist/esm/services/authProvider/index.d.ts +0 -63
  417. package/dist/esm/services/authProvider/index.js +0 -26
  418. package/dist/esm/services/authProvider/subrequest.d.ts +0 -13
  419. package/dist/esm/services/authProvider/subrequest.js +0 -45
  420. package/dist/esm/services/authProvider/utils/decryptAndVerify.d.ts +0 -28
  421. package/dist/esm/services/authProvider/utils/decryptAndVerify.js +0 -85
  422. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.d.ts +0 -26
  423. package/dist/esm/services/authProvider/utils/embeddedAuthProvider.js +0 -40
  424. package/dist/esm/services/authProvider/utils/userRoleValidator.d.ts +0 -13
  425. package/dist/esm/services/authProvider/utils/userRoleValidator.js +0 -33
  426. package/dist/esm/services/authProvider/utils/userSubrequest.d.ts +0 -3
  427. package/dist/esm/services/authProvider/utils/userSubrequest.js +0 -6
  428. package/dist/esm/services/documentStore/dynamoEncoding.d.ts +0 -10
  429. package/dist/esm/services/documentStore/dynamoEncoding.js +0 -45
  430. package/dist/esm/services/documentStore/fileSystemAssert.d.ts +0 -1
  431. package/dist/esm/services/documentStore/fileSystemAssert.js +0 -10
  432. package/dist/esm/services/documentStore/index.d.ts +0 -14
  433. package/dist/esm/services/documentStore/index.js +0 -1
  434. package/dist/esm/services/documentStore/unversioned/dynamodb.d.ts +0 -31
  435. package/dist/esm/services/documentStore/unversioned/dynamodb.js +0 -226
  436. package/dist/esm/services/documentStore/unversioned/file-system.d.ts +0 -32
  437. package/dist/esm/services/documentStore/unversioned/file-system.js +0 -174
  438. package/dist/esm/services/documentStore/unversioned/index.d.ts +0 -2
  439. package/dist/esm/services/documentStore/unversioned/index.js +0 -1
  440. package/dist/esm/services/documentStore/versioned/dynamodb.d.ts +0 -25
  441. package/dist/esm/services/documentStore/versioned/dynamodb.js +0 -139
  442. package/dist/esm/services/documentStore/versioned/file-system.d.ts +0 -25
  443. package/dist/esm/services/documentStore/versioned/file-system.js +0 -69
  444. package/dist/esm/services/documentStore/versioned/index.d.ts +0 -17
  445. package/dist/esm/services/documentStore/versioned/index.js +0 -1
  446. package/dist/esm/services/exercisesGateway/index.d.ts +0 -67
  447. package/dist/esm/services/exercisesGateway/index.js +0 -70
  448. package/dist/esm/services/fileServer/index.d.ts +0 -30
  449. package/dist/esm/services/fileServer/index.js +0 -13
  450. package/dist/esm/services/fileServer/localFileServer.d.ts +0 -13
  451. package/dist/esm/services/fileServer/localFileServer.js +0 -125
  452. package/dist/esm/services/fileServer/s3FileServer.d.ts +0 -14
  453. package/dist/esm/services/fileServer/s3FileServer.js +0 -124
  454. package/dist/esm/services/launchParams/index.d.ts +0 -2
  455. package/dist/esm/services/launchParams/index.js +0 -2
  456. package/dist/esm/services/launchParams/signer.d.ts +0 -23
  457. package/dist/esm/services/launchParams/signer.js +0 -51
  458. package/dist/esm/services/launchParams/verifier.d.ts +0 -21
  459. package/dist/esm/services/launchParams/verifier.js +0 -92
  460. package/dist/esm/services/logger/console.d.ts +0 -4
  461. package/dist/esm/services/logger/index.js +0 -27
  462. package/dist/esm/services/lrsGateway/addStatementDefaultFields.d.ts +0 -5
  463. package/dist/esm/services/lrsGateway/addStatementDefaultFields.js +0 -14
  464. package/dist/esm/services/lrsGateway/attempt-utils.d.ts +0 -70
  465. package/dist/esm/services/lrsGateway/attempt-utils.js +0 -236
  466. package/dist/esm/services/lrsGateway/file-system.d.ts +0 -15
  467. package/dist/esm/services/lrsGateway/file-system.js +0 -110
  468. package/dist/esm/services/lrsGateway/index.d.ts +0 -122
  469. package/dist/esm/services/lrsGateway/index.js +0 -111
  470. package/dist/esm/services/lrsGateway/xapiUtils.d.ts +0 -68
  471. package/dist/esm/services/lrsGateway/xapiUtils.js +0 -99
  472. package/dist/esm/services/postgresConnection/index.d.ts +0 -28
  473. package/dist/esm/services/postgresConnection/index.js +0 -58
  474. package/dist/esm/services/searchProvider/index.d.ts +0 -67
  475. package/dist/esm/services/searchProvider/index.js +0 -1
  476. package/dist/esm/services/searchProvider/memorySearchTheBadWay.d.ts +0 -20
  477. package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +0 -187
  478. package/dist/esm/services/searchProvider/openSearch.d.ts +0 -28
  479. package/dist/esm/services/searchProvider/openSearch.js +0 -150
  480. package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +0 -1
  481. package/dist/esm/types.js +0 -1
  482. /package/{script → packages/utils/script}/bin/copy-from-template.bash +0 -0
  483. /package/{script → packages/utils/script}/bin/delete-stack.bash +0 -0
  484. /package/{script → packages/utils/script}/bin/deploy.bash +0 -0
  485. /package/{script → packages/utils/script}/bin/destroy-deployment.bash +0 -0
  486. /package/{script → packages/utils/script}/bin/empty-bucket.bash +0 -0
  487. /package/{script → packages/utils/script}/bin/get-arg.bash +0 -0
  488. /package/{script → packages/utils/script}/bin/get-deployed-environments.bash +0 -0
  489. /package/{script → packages/utils/script}/bin/get-env-param.bash +0 -0
  490. /package/{script → packages/utils/script}/bin/get-kwarg.bash +0 -0
  491. /package/{script → packages/utils/script}/bin/get-stack-param.bash +0 -0
  492. /package/{script → packages/utils/script}/bin/has-flag.bash +0 -0
  493. /package/{script → packages/utils/script}/bin/init-constants-script.bash +0 -0
  494. /package/{script → packages/utils/script}/bin/init-params-script.bash +0 -0
  495. /package/{script → packages/utils/script}/bin/stack-exists.bash +0 -0
  496. /package/{script → packages/utils/script}/bin/update-utils.bash +0 -0
  497. /package/{script → packages/utils/script}/bin/upload-pager-duty-endpoints.bash +0 -0
  498. /package/{script → packages/utils/script}/bin/upload-params.bash +0 -0
  499. /package/{script → packages/utils/script}/bin/which.bash +0 -0
  500. /package/{script → packages/utils/script}/bin-entry.bash +0 -0
  501. /package/{script → packages/utils/script}/build.bash +0 -0
  502. /package/{dist/cjs/index.d.ts → packages/utils/src/index.ts} +0 -0
  503. /package/{dist/cjs/services/launchParams/index.d.ts → packages/utils/src/services/launchParams/index.ts} +0 -0
@@ -0,0 +1,155 @@
1
+ /* cspell:ignore presigner */
2
+ import path from 'path';
3
+ import { CopyObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
4
+ import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
5
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
6
+ import { v4 as uuid } from 'uuid';
7
+ import { once } from '../..';
8
+ import { assertDefined } from '../../assertions';
9
+ import { ConfigProviderForConfig, resolveConfigValue } from '../../config';
10
+ import { ifDefined } from '../../guards';
11
+ import { FileServerAdapter, FileValue } from '.';
12
+
13
+ export type Config = {
14
+ bucketName: string;
15
+ bucketRegion: string;
16
+ // optional, if not provided attempting to get a public URL will throw an error
17
+ publicViewerDomain?: string;
18
+ };
19
+ interface Initializer<C> {
20
+ configSpace?: C;
21
+ getS3Client?: (...args: ConstructorParameters<typeof S3Client>) => S3Client;
22
+ }
23
+
24
+ export const s3FileServer = <C extends string = 'deployed'>(initializer: Initializer<C>) => (configProvider: {[_key in C]: ConfigProviderForConfig<Config>}): FileServerAdapter => {
25
+ const config = configProvider[ifDefined(initializer.configSpace, 'deployed' as C)];
26
+ const bucketName = once(() => resolveConfigValue(config.bucketName));
27
+ const bucketRegion = once(() => resolveConfigValue(config.bucketRegion));
28
+ const publicViewerDomain = once(() => 'publicViewerDomain' in config && config.publicViewerDomain
29
+ ? resolveConfigValue(config.publicViewerDomain)
30
+ : undefined
31
+ );
32
+ const s3Service = once(async () => {
33
+ const args = {apiVersion: '2012-08-10', region: await bucketRegion()};
34
+ return initializer.getS3Client?.(args) ?? new S3Client(args);
35
+ });
36
+
37
+ /*
38
+ * https://docs.aws.amazon.com/AmazonS3/latest/userguide/ShareObjectPreSignedURL.html
39
+ */
40
+ const getSignedViewerUrl = async (source: FileValue) => {
41
+ const bucket = (await bucketName());
42
+ const command = new GetObjectCommand({Bucket: bucket, Key: source.path});
43
+ return getSignedUrl(await s3Service(), command, {
44
+ expiresIn: 3600, // 1 hour
45
+ });
46
+ };
47
+ const getPublicViewerUrl = async (source: FileValue) => {
48
+ const host = assertDefined(await publicViewerDomain(),
49
+ new Error(`Tried to get public viewer URL for ${source.path} but no publicViewerDomain configured`)
50
+ );
51
+ return `https://${host}/${source.path}`;
52
+ };
53
+ const getFileContent = async (source: FileValue) => {
54
+ const bucket = await bucketName();
55
+ const command = new GetObjectCommand({Bucket: bucket, Key: source.path});
56
+ const response = await (await s3Service()).send(command);
57
+
58
+ return Buffer.from(await assertDefined(response.Body, new Error('Invalid Response from s3')).transformToByteArray());
59
+ };
60
+ const putFileContent = async (source: FileValue, content: string) => {
61
+ const bucket = await bucketName();
62
+ const command = new PutObjectCommand({
63
+ Bucket: bucket,
64
+ Key: source.path,
65
+ Body: content,
66
+ ContentType: source.mimeType,
67
+ });
68
+ await (await s3Service()).send(command);
69
+ return source;
70
+ };
71
+
72
+ /*
73
+ * https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_presigned_post.html
74
+ * https://docs.aws.amazon.com/AmazonS3/latest/userguide/HTTPPOSTExamples.html
75
+ * https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
76
+ */
77
+ const getSignedFileUploadConfig = async() => {
78
+ const prefix = 'uploads/' + uuid();
79
+ const bucket = (await bucketName());
80
+
81
+ const Conditions = [
82
+ { acl: 'private' },
83
+ { bucket } as {bucket: string},
84
+ ['starts-with', '$key', prefix] as ['starts-with', string, string]
85
+ ];
86
+
87
+ const defaultFields = {
88
+ acl: 'private',
89
+ };
90
+ const { url, fields } = await createPresignedPost(await s3Service(), {
91
+ Bucket: bucket,
92
+ Key: prefix + '/${filename}',
93
+ Conditions,
94
+ Fields: defaultFields,
95
+ Expires: 3600, // 1 hour
96
+ });
97
+
98
+ return {
99
+ url, payload: fields
100
+ };
101
+ };
102
+
103
+ const copyFileTo = async (source: FileValue, destinationPath: string) => {
104
+ const bucket = (await bucketName());
105
+ const destinationPathWithoutLeadingSlash = destinationPath.replace(/^\//, '');
106
+
107
+ const command = new CopyObjectCommand({
108
+ Bucket: bucket,
109
+ Key: destinationPathWithoutLeadingSlash,
110
+ CopySource: path.join(bucket, source.path),
111
+ });
112
+ await (await s3Service()).send(command);
113
+
114
+ return {
115
+ ...source,
116
+ path: destinationPathWithoutLeadingSlash
117
+ };
118
+ };
119
+
120
+ const copyFileToDirectory = async (source: FileValue, destination: string) => {
121
+ const destinationPath = path.join(destination, source.label);
122
+ return copyFileTo(source, destinationPath);
123
+ };
124
+
125
+ const isTemporaryUpload = (source: FileValue) => {
126
+ return source.path.indexOf('uploads/') === 0;
127
+ };
128
+
129
+ const getFileChecksum = async (source: FileValue) => {
130
+ const bucket = (await bucketName());
131
+ const command = new HeadObjectCommand({Bucket: bucket, Key: source.path});
132
+ const response = await (await s3Service()).send(command);
133
+ return assertDefined(response.ETag);
134
+ };
135
+
136
+ const filesEqual = async (sourceA: FileValue, sourceB: FileValue) => {
137
+ const [aSum, bSum] = await Promise.all(
138
+ [getFileChecksum(sourceA), getFileChecksum(sourceB)]
139
+ );
140
+ return aSum === bSum;
141
+ };
142
+
143
+ return {
144
+ getFileContent,
145
+ putFileContent,
146
+ getSignedViewerUrl,
147
+ getPublicViewerUrl,
148
+ getSignedFileUploadConfig,
149
+ copyFileTo,
150
+ copyFileToDirectory,
151
+ isTemporaryUpload,
152
+ getFileChecksum,
153
+ filesEqual,
154
+ };
155
+ };
@@ -0,0 +1,366 @@
1
+ /* spell-checker: ignore Xgub Jpbmci Blbn Imlzcy XVCIs */
2
+ import jwt from 'jsonwebtoken';
3
+ import { JwksClient } from 'jwks-rsa';
4
+ import ms from 'ms';
5
+ import { z } from 'zod';
6
+ import { SessionExpiredError } from '../../errors';
7
+ import { createLaunchSigner, createLaunchVerifier } from '.';
8
+
9
+ const alg = 'RS256' as const;
10
+ const expiresIn = '1m';
11
+ const iss = 'https://openstax.org';
12
+ // privateKey generated for testing only
13
+ const privateKey = `-----BEGIN PRIVATE KEY-----
14
+ MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdyPcrfMDym/eS
15
+ jFEddXdbVh5PhvcikruXvzyJ/Ehocd0lgV/OMaSvdSqbmw1lJvC7NwuuhnslxwoD
16
+ 0zHaUq+XR6dkaw09wPG4jzj76FByKyvFOKB8E+z3ARFDZkiRK6rdVTVnVwFG0TCl
17
+ Zwdb6FSX8DL8qOqRIDzxuXBmp02Y79srcarYpaHKwwOBt8Fjs8XSADj6OR+/abXP
18
+ 5cAwtqM/P18efcSfGVqoraJMSNh7tJGTdOMiSrSY8n/F7xJytZf7YeVoppjIdjvG
19
+ TyHf3R3vg6xFJP63ISE1fdz8gDHR/Ix6kmWQN+t83/uYcxmcmA5CgszJXtKNxKAy
20
+ WtucJhj7AgMBAAECggEAWNKuov0ArWlneoq1xc2QssTHuOP9wBvyA3E0hrLCMQpB
21
+ Hk81FnhtU1Pib5VmhPdWfDQseoYjddrEGQoRMjXhWv34fVAeangGlvrNQJ4m8cJ2
22
+ cjMemvLD+Zy01EB3maRadvA0RiHjQgBvsh6UJOvkTUGGugfQgbTek5TH/mTMwA24
23
+ 02DsewnNk5UnLkUsh5tDeJkq9UhI2sSdkLkUoUAyZYXdUF0RRxotPK9IIorA22Vu
24
+ Hw/WQ3Sr8RDyNDW0eVsmvh2jWj+vTKndqvryNqqHztpxeXh3BC9/flZjLZPgAFZ9
25
+ NVnB3nj2a3A9kqz/gu/jyK1DoC9e7a8xnetp1KxK+QKBgQD07n9yBmSh06kL71Sn
26
+ 8YXD5BGKWCLW+w5GM2EqzwtwXy5TGP6A6ctQESQycYEer9P0PfOG8/T4eHe0xleB
27
+ OEVM/k+FfaxHuY3FU09gb0X04jKZnE3D9gNEzJfuejb12RLJkbFPGpzmrOSy+rgb
28
+ Kw9LUmppk3tkNE9aTGR+Wyj+5wKBgQDnzrHyorFuomebcih5HEXfozHg2XmLUwlW
29
+ ajF9c/VPKcLuzDsK0v10ZjLY30mR9i9tcwURb+YGMo3RxAxAXwcUvKzsi32fYpnn
30
+ oVn2vJeauasTL+NjKp/8SSlVQykyuVNHc5WD+CrfyMRuDQWitmZVmH0oX38xejML
31
+ OttDAaD2zQKBgQDieRySnGK0V3f5tyR53ZMoHFwzpWchjSYXty5jXOpgz0GQSI4b
32
+ ORJrSE5F7jnL3ByJvDbPVAfxL8LP/o2uyA8cMBHrhtajw2jZSj0dGAaBdh3/d01Y
33
+ osNG/D6Hna3wCPWcOADQ+fSWPsuAyt8tD9WmeeA3Gs1/S/cLsZNCWpw/hQKBgQDi
34
+ MbE2gEb/G/5ZksWnmE0ecpJjRBL0PuynpCvyrMo8mxWaCzO51jR39pm7Qfb6NO4G
35
+ sOvLHhOpDH+SpukFjzTLqWhb0amdc4uWmL2+pDyTQIWMzXfm9lryxTlN96El2+1F
36
+ laXaBrlfcPssIAzVv4KQF6JZWQY2c3WHicEW22oEKQKBgHt6n+j0ZO+Quq7k7FNr
37
+ 6RAKDmAURh/+rPc3o357inI9u5oxktXvxI41ijHXTQRKrakAmiSYikPQOb/aOgjO
38
+ PNpj6gag4Srw7QkLI8ttl0yUCP155dxvnrK8Dd/t+oTsKTCR0EINSUUM8omDnHCL
39
+ 5kaLB/QpHAKj0pF54unD8VzI
40
+ -----END PRIVATE KEY-----`;
41
+
42
+ const signerConfig = {
43
+ test: {
44
+ alg,
45
+ expiresIn,
46
+ iss,
47
+ privateKey,
48
+ }
49
+ };
50
+
51
+ const launchSigner = createLaunchSigner({ configSpace: 'test' })(signerConfig);
52
+
53
+ describe('launchSigner', () => {
54
+ describe('jwks', () => {
55
+ it('outputs the public part of the key', async() => {
56
+ // cspell:disable
57
+ expect(await launchSigner.jwks()).toMatchInlineSnapshot(`
58
+ {
59
+ "keys": [
60
+ {
61
+ "e": "AQAB",
62
+ "kid": "wKgbD-KbvgA_GIKnggurrTGH_tGYVI8cDU01BuM6zC8",
63
+ "kty": "RSA",
64
+ "n": "3cj3K3zA8pv3koxRHXV3W1YeT4b3IpK7l788ifxIaHHdJYFfzjGkr3Uqm5sNZSbwuzcLroZ7JccKA9Mx2lKvl0enZGsNPcDxuI84--hQcisrxTigfBPs9wERQ2ZIkSuq3VU1Z1cBRtEwpWcHW-hUl_Ay_KjqkSA88blwZqdNmO_bK3Gq2KWhysMDgbfBY7PF0gA4-jkfv2m1z-XAMLajPz9fHn3EnxlaqK2iTEjYe7SRk3TjIkq0mPJ_xe8ScrWX-2HlaKaYyHY7xk8h390d74OsRST-tyEhNX3c_IAx0fyMepJlkDfrfN_7mHMZnJgOQoLMyV7SjcSgMlrbnCYY-w",
65
+ },
66
+ ],
67
+ }
68
+ `);
69
+ // cspell:enable
70
+ });
71
+ });
72
+
73
+ describe('sign', () => {
74
+ let publicKey: string;
75
+
76
+ beforeEach(async() => {
77
+ const jwksUri = 'https://localhost/test';
78
+ const fetcher = (uri: string) => {
79
+ if (uri !== jwksUri) { throw new Error(`Unexpected URI: "${uri}"`); }
80
+ return launchSigner.jwks();
81
+ };
82
+ const jwksClient = new JwksClient({ fetcher, jwksUri });
83
+ const key = await jwksClient.getSigningKey();
84
+ publicKey = key.getPublicKey();
85
+ });
86
+
87
+ it('returns a signed token', async() => {
88
+ const token = await launchSigner.sign({}, 'userId');
89
+ const { header, payload } = jwt.verify(token, publicKey, { complete: true });
90
+ if (typeof payload !== 'object') { throw new Error('Unexpected string payload'); }
91
+
92
+ expect(header.alg).toBe(alg);
93
+ expect(header.typ).toBe('JWT');
94
+
95
+ expect(payload.exp).toBeLessThanOrEqual(Math.floor(Date.now() / 1000) + 60);
96
+ expect(payload.sub).toBe('userId');
97
+ });
98
+
99
+ it('can set token duration using a unix timestamp', async() => {
100
+ const maxExp = Math.floor(Date.now() / 1000) + 30;
101
+ const token = await launchSigner.sign({}, 'userId', maxExp);
102
+ const { payload } = jwt.verify(token, publicKey, { complete: true });
103
+ if (typeof payload !== 'object') { throw new Error('Unexpected string payload'); }
104
+ expect(payload.exp).toBeLessThanOrEqual(maxExp);
105
+ expect(payload.exp).toBeGreaterThanOrEqual(maxExp - 5);
106
+ });
107
+
108
+ it('cannot exceed the configured max token duration', async() => {
109
+ const now = Math.floor(Date.now()/1000);
110
+ const maxExp = now + 86400; // 1 day
111
+ const actualMax = now + Math.floor(ms(expiresIn)/1000);
112
+ const token = await launchSigner.sign({}, 'userId', maxExp);
113
+ const { payload } = jwt.verify(token, publicKey, { complete: true });
114
+ if (typeof payload !== 'object') { throw new Error('Unexpected string payload'); }
115
+ expect(payload.exp).toBeLessThanOrEqual(actualMax);
116
+ expect(payload.exp).toBeGreaterThanOrEqual(actualMax - 5);
117
+ });
118
+
119
+ it('throws when alg is invalid', () => {
120
+ const invalidLaunchSigner = createLaunchSigner({ configSpace: 'test' })({ test: { ...signerConfig.test, alg: 'none' } });
121
+ return expect(invalidLaunchSigner.sign({}, 'userId')).rejects.toThrow('"none" is not a valid algorithm');
122
+ });
123
+ });
124
+ });
125
+
126
+ describe('launchVerifier', () => {
127
+ const verifierConfig = {
128
+ test: {
129
+ trustedDomain: 'openstax.org',
130
+ bypassSignatureVerification: 'false',
131
+ }
132
+ };
133
+
134
+ const fetcher = (uri: string) => {
135
+ if (uri !== new URL('/.well-known/jwks.json', iss).toString()) { throw new Error(`Unexpected URI: "${uri}"`); }
136
+ return launchSigner.jwks();
137
+ };
138
+
139
+ const services = {};
140
+ const launchVerifier = createLaunchVerifier({ configSpace: 'test', fetcher })(verifierConfig);
141
+
142
+ describe('with legacy format token', () => {
143
+ it('can verify string value tokens signed by the launchSigner reading value from .sub', async() => {
144
+ const token = await launchSigner.sign({}, 'testSuccessful=true');
145
+ expect((await launchVerifier(services).verify(token)).sub).toMatchInlineSnapshot('"testSuccessful=true"');
146
+ });
147
+
148
+ it('can verify json value tokens signed by the launchSigner reading value from .sub', async() => {
149
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
150
+ expect((await launchVerifier(services).verify(token)).sub).toMatchInlineSnapshot('"{"testSuccessful":true}"');
151
+ });
152
+
153
+ it('can verify json value tokens signed by the launchSigner reading value from token', async() => {
154
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
155
+ expect((await launchVerifier(services).verify(token)).testSuccessful).toMatchInlineSnapshot('true');
156
+ });
157
+
158
+ it('can verify extended claim tokens signed by the launchSigner reading value from token', async() => {
159
+ const token = await launchSigner.sign({testSuccessful:true}, 'userId');
160
+ expect((await launchVerifier(services).verify(token)).testSuccessful).toMatchInlineSnapshot('true');
161
+ });
162
+
163
+ describe('with a validator', () => {
164
+ it('can use a default token', async() => {
165
+ const token = await launchSigner.sign({}, 'userId');
166
+ const validator = z.object({sub: z.string()});
167
+ expect((await launchVerifier(services, () => token).verify(validator.parse)).sub).toBe('userId');
168
+ });
169
+
170
+ it('throws with no token provided', async() => {
171
+ const validator = z.object({sub: z.string()});
172
+ await expect(() => launchVerifier(services).verify(validator.parse)).rejects
173
+ .toThrowErrorMatchingInlineSnapshot('"Missing token for launch verification"')
174
+ ;
175
+ });
176
+
177
+ it('can verify string value tokens signed by the launchSigner reading value from .sub', async() => {
178
+ const token = await launchSigner.sign({}, 'testSuccessful=true');
179
+ const validator = z.object({sub: z.string()});
180
+ expect((await launchVerifier(services).verify(token, validator.parse)).sub).toMatchInlineSnapshot('"testSuccessful=true"');
181
+ });
182
+
183
+ it('can verify json value tokens signed by the launchSigner reading value from .sub', async() => {
184
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
185
+ const validator = z.object({sub: z.string()});
186
+ expect((await launchVerifier(services).verify(token, validator.parse)).sub).toMatchInlineSnapshot('"{"testSuccessful":true}"');
187
+ });
188
+
189
+ it('can verify json value tokens signed by the launchSigner reading extracted value', async() => {
190
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
191
+ const validator = z.object({testSuccessful: z.boolean()});
192
+ expect((await launchVerifier(services).verify(token, validator.parse)).testSuccessful).toMatchInlineSnapshot('true');
193
+ });
194
+
195
+ it('can verify extended claim tokens signed by the launchSigner reading extracted value', async() => {
196
+ const token = await launchSigner.sign({testSuccessful:true}, 'userId');
197
+ const validator = z.object({testSuccessful: z.boolean()});
198
+ expect((await launchVerifier(services).verify(token, validator.parse)).testSuccessful).toMatchInlineSnapshot('true');
199
+ });
200
+
201
+ it('strips unknown claims from result', async() => {
202
+ const token = await launchSigner.sign({}, '{"testSuccessful":true,"unknown":"value"}');
203
+ const validator = z.object({sub: z.string(), iss: z.string(), testSuccessful: z.boolean()});
204
+ expect(await launchVerifier(services).verify(token, validator.parse)).toMatchInlineSnapshot(`
205
+ {
206
+ "iss": "https://openstax.org",
207
+ "sub": "{"testSuccessful":true,"unknown":"value"}",
208
+ "testSuccessful": true,
209
+ }
210
+ `);
211
+ });
212
+
213
+ it('throws on invalid token parameters', async() => {
214
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
215
+ const validator = z.object({testSuccessfullyBad: z.boolean()});
216
+ await expect(() => launchVerifier(services).verify(token, validator.parse)).rejects.toThrowErrorMatchingInlineSnapshot(`
217
+ "error in secret or public key callback: [
218
+ {
219
+ "expected": "boolean",
220
+ "code": "invalid_type",
221
+ "path": [
222
+ "testSuccessfullyBad"
223
+ ],
224
+ "message": "Invalid input: expected boolean, received undefined"
225
+ }
226
+ ]"
227
+ `);
228
+ });
229
+ });
230
+ });
231
+
232
+ it('throws when the token is invalid', () => {
233
+ const token = 'R4ND0M';
234
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow('jwt malformed');
235
+ });
236
+
237
+ it('throws when the token header is missing the iss claim', () => {
238
+ const token = jwt.sign({}, privateKey, { algorithm: alg, expiresIn, issuer: iss });
239
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow('error in secret or public key callback: JWT header missing iss claim');
240
+ });
241
+
242
+ it('throws when the token is expired', () => {
243
+ const header = { alg, iss };
244
+ const token = jwt.sign({}, privateKey, { algorithm: alg, expiresIn: -1, header });
245
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow(SessionExpiredError);
246
+ });
247
+
248
+ it('throws when the token iss is an invalid URL', () => {
249
+ const issuer = 'openstax.org';
250
+ const header = { alg, iss: issuer };
251
+ const token = jwt.sign({}, privateKey, { algorithm: alg, expiresIn, header, issuer });
252
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow('error in secret or public key callback: Invalid URL');
253
+ });
254
+
255
+ it('throws when the token iss is untrusted', () => {
256
+ const issuer = 'https://example.com';
257
+ const header = { alg, iss: issuer };
258
+ const token = jwt.sign({}, privateKey, { algorithm: alg, expiresIn, header, issuer });
259
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow(
260
+ 'error in secret or public key callback: Untrusted launch domain: "example.com"'
261
+ );
262
+ });
263
+
264
+ it('throws when the token payload is a string', () => {
265
+ const header = { alg, iss };
266
+ const token = jwt.sign('testSuccessful=true', privateKey, { header });
267
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow('received JWT token with unexpected non-JSON payload');
268
+ });
269
+
270
+ it('throws when the token payload is missing the sub claim', () => {
271
+ const header = { alg, iss };
272
+ const token = jwt.sign({}, privateKey, { expiresIn, header, issuer: iss });
273
+ return expect(launchVerifier(services).verify(token)).rejects.toThrow('JWT payload missing sub claim');
274
+ });
275
+
276
+ describe('with bypassSignatureVerification enabled', () => {
277
+ const bypassVerifierConfig = {
278
+ test: {
279
+ trustedDomain: 'openstax.org',
280
+ bypassSignatureVerification: 'true',
281
+ }
282
+ };
283
+
284
+ const bypassLaunchVerifier = createLaunchVerifier({ configSpace: 'test', fetcher })(bypassVerifierConfig);
285
+
286
+ it('can verify tokens without signature verification', async() => {
287
+ const token = await launchSigner.sign({}, 'testSuccessful=true');
288
+ expect((await bypassLaunchVerifier(services).verify(token)).sub).toMatchInlineSnapshot('"testSuccessful=true"');
289
+ });
290
+
291
+ it('can verify json value tokens reading value from token', async() => {
292
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
293
+ expect((await bypassLaunchVerifier(services).verify(token)).testSuccessful).toMatchInlineSnapshot('true');
294
+ });
295
+
296
+ it('can verify extended claim tokens reading value from token', async() => {
297
+ const token = await launchSigner.sign({testSuccessful:true}, 'userId');
298
+ expect((await bypassLaunchVerifier(services).verify(token)).testSuccessful).toMatchInlineSnapshot('true');
299
+ });
300
+
301
+ it('can verify tokens with invalid signatures when bypass is enabled', async() => {
302
+ // Create a malformed token that would normally fail signature verification
303
+ // but should succeed when bypass is enabled
304
+ const validToken = await launchSigner.sign({testSuccessful: true}, 'userId');
305
+ const [header, payload] = validToken.split('.');
306
+ const invalidToken = `${header}.${payload}.invalid_signature`;
307
+
308
+ expect((await bypassLaunchVerifier(services).verify(invalidToken)).testSuccessful).toBe(true);
309
+ });
310
+
311
+ it('can verify expired tokens when bypass is enabled', async() => {
312
+ const expiredToken = jwt.sign({testSuccessful: true}, privateKey, {
313
+ algorithm: alg,
314
+ expiresIn: -1,
315
+ subject: 'userId',
316
+ issuer: iss
317
+ });
318
+
319
+ expect((await bypassLaunchVerifier(services).verify(expiredToken)).testSuccessful).toBe(true);
320
+ });
321
+
322
+ it('still throws when the token payload is missing the sub claim', async() => {
323
+ const tokenWithoutSub = jwt.sign({testSuccessful: true}, privateKey, {
324
+ algorithm: alg,
325
+ expiresIn,
326
+ issuer: iss
327
+ });
328
+
329
+ await expect(bypassLaunchVerifier(services).verify(tokenWithoutSub)).rejects.toThrow('JWT payload missing sub claim');
330
+ });
331
+
332
+ it('still throws when the token payload is not an object', async() => {
333
+ // Create a malformed token that would decode to a string
334
+ const malformedToken = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImlzcyI6Imh0dHBzOi8vb3BlbnN0YXgub3JnIn0.InRlc3RTdHJpbmci.signature';
335
+
336
+ await expect(bypassLaunchVerifier(services).verify(malformedToken)).rejects.toThrow('received JWT token with unexpected non-JSON payload');
337
+ });
338
+
339
+ it('works with validator when bypass is enabled', async() => {
340
+ const token = await launchSigner.sign({}, '{"testSuccessful":true}');
341
+ const validator = z.object({testSuccessful: z.boolean()});
342
+ expect((await bypassLaunchVerifier(services).verify(token, validator.parse)).testSuccessful).toBe(true);
343
+ });
344
+
345
+ });
346
+
347
+ describe('with bypassSignatureVerification disabled', () => {
348
+ const normalVerifierConfig = {
349
+ test: {
350
+ trustedDomain: 'openstax.org',
351
+ bypassSignatureVerification: 'false',
352
+ }
353
+ };
354
+
355
+ const normalLaunchVerifier = createLaunchVerifier({ configSpace: 'test', fetcher })(normalVerifierConfig);
356
+
357
+ it('still performs signature verification when bypass is explicitly disabled', async() => {
358
+ // Create a token with invalid signature that should fail verification when bypass is disabled
359
+ const validToken = await launchSigner.sign({testSuccessful: true}, 'userId');
360
+ const [header, payload] = validToken.split('.');
361
+ const invalidToken = `${header}.${payload}.invalid_signature`;
362
+
363
+ await expect(normalLaunchVerifier(services).verify(invalidToken)).rejects.toThrow();
364
+ });
365
+ });
366
+ });
@@ -0,0 +1,73 @@
1
+ import jwt from 'jsonwebtoken';
2
+ import ms from 'ms';
3
+ import { JWK } from 'node-jose';
4
+ import { once } from '../..';
5
+ import { ConfigProviderForConfig, resolveConfigValue } from '../../config';
6
+ import { ifDefined } from '../../guards';
7
+ import type { JsonCompatibleStruct } from '../../routing';
8
+
9
+ type Config = {
10
+ alg: string;
11
+ expiresIn: string;
12
+ iss: string;
13
+ privateKey: string;
14
+ };
15
+ interface Initializer<C> {
16
+ configSpace?: C;
17
+ }
18
+
19
+ // jsonwebtoken does not export this list
20
+ type SupportedAlgorithm = Exclude<jwt.Algorithm, 'none'>;
21
+ const SUPPORTED_ALGORITHMS: SupportedAlgorithm[] & ReadonlyArray<string> = [
22
+ 'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'PS256', 'PS384', 'PS512'
23
+ ];
24
+ const assertAlg = (alg: string) => {
25
+ if ((SUPPORTED_ALGORITHMS).includes(alg)) {
26
+ return alg as SupportedAlgorithm;
27
+ }
28
+
29
+ throw new Error(`"${alg}" is not a valid algorithm`);
30
+ };
31
+
32
+ /**
33
+ * Creates a class that can sign launch params
34
+ */
35
+ export const createLaunchSigner = <C extends string = 'launch'>({configSpace}: Initializer<C>) => (configProvider: {[_key in C]: ConfigProviderForConfig<Config>}) => {
36
+ const config = configProvider[ifDefined(configSpace, 'launch' as C)];
37
+ const getAlg = once(async() => assertAlg(await resolveConfigValue(config.alg)));
38
+ const getExpiresIn = once(() => resolveConfigValue(config.expiresIn));
39
+ const getIss = once(() => resolveConfigValue(config.iss));
40
+ const getPrivateKey = once(() => resolveConfigValue(config.privateKey));
41
+ const getKeyStore = once(async() => {
42
+ const keystore = JWK.createKeyStore();
43
+ await keystore.add(await getPrivateKey(), 'pem');
44
+ return keystore;
45
+ });
46
+
47
+ const jwks = async() => (await getKeyStore()).toJSON(false) as { keys: JWK.RawKey[] };
48
+
49
+ const getExpiresInWithMax = async(maxExp?: number | null) => {
50
+ const expiresIn = await getExpiresIn();
51
+
52
+ // The ms library used by jsonwebtoken can handle a value in seconds as well as a string like '1d'
53
+ if (!maxExp) { return expiresIn; }
54
+
55
+ // Convert both values to seconds for comparison
56
+ const expiresInSeconds = Math.floor(ms(expiresIn)/1000);
57
+ const maxExpSeconds = maxExp - Math.floor(Date.now()/1000);
58
+ return Math.min(expiresInSeconds, maxExpSeconds);
59
+ };
60
+
61
+ const sign = async(data: JsonCompatibleStruct, subject: string, maxExp?: number | null) => {
62
+ const alg = await getAlg();
63
+ // expiresIn can be a number of seconds or a string like '1h' or '1d'
64
+ const expiresIn = await getExpiresInWithMax(maxExp);
65
+ const iss = await getIss();
66
+ const header = { alg, iss };
67
+ return jwt.sign(data, await getPrivateKey(), { algorithm: alg, expiresIn, header, issuer: iss, subject });
68
+ };
69
+
70
+ return { jwks, sign };
71
+ };
72
+
73
+ export type LaunchSigner = ReturnType<ReturnType<typeof createLaunchSigner>>;