@atproto/oauth-provider 0.3.1 → 0.5.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 (404) hide show
  1. package/.linguirc +57 -0
  2. package/CHANGELOG.md +29 -0
  3. package/LICENSE.txt +1 -1
  4. package/dist/account/account-manager.d.ts +17 -3
  5. package/dist/account/account-manager.d.ts.map +1 -1
  6. package/dist/account/account-manager.js +102 -8
  7. package/dist/account/account-manager.js.map +1 -1
  8. package/dist/account/account-store.d.ts +81 -15
  9. package/dist/account/account-store.d.ts.map +1 -1
  10. package/dist/account/account-store.js +70 -19
  11. package/dist/account/account-store.js.map +1 -1
  12. package/dist/account/sign-in-data.d.ts +28 -0
  13. package/dist/account/sign-in-data.d.ts.map +1 -0
  14. package/dist/account/sign-in-data.js +16 -0
  15. package/dist/account/sign-in-data.js.map +1 -0
  16. package/dist/account/sign-up-data.d.ts +26 -0
  17. package/dist/account/sign-up-data.d.ts.map +1 -0
  18. package/dist/account/sign-up-data.js +11 -0
  19. package/dist/account/sign-up-data.js.map +1 -0
  20. package/dist/assets/app/bundle-manifest.json +598 -6
  21. package/dist/assets/app/index-ItwwtJ8r.js +36 -0
  22. package/dist/assets/app/index-ItwwtJ8r.js.map +1 -0
  23. package/dist/assets/app/main-B_dNxQo_.js +4 -0
  24. package/dist/assets/app/main-B_dNxQo_.js.map +1 -0
  25. package/dist/assets/app/main-CSatvmRR.css +3 -0
  26. package/dist/assets/app/main-CSatvmRR.js +306 -0
  27. package/dist/assets/app/main-CSatvmRR.js.map +1 -0
  28. package/dist/assets/app/messages-BQeltXSF.js +4 -0
  29. package/dist/assets/app/messages-BQeltXSF.js.map +1 -0
  30. package/dist/assets/app/messages-BQkEhfjg.js +4 -0
  31. package/dist/assets/app/messages-BQkEhfjg.js.map +1 -0
  32. package/dist/assets/app/messages-BUjKj_UJ.js +4 -0
  33. package/dist/assets/app/messages-BUjKj_UJ.js.map +1 -0
  34. package/dist/assets/app/messages-BWIQa8fO.js +4 -0
  35. package/dist/assets/app/messages-BWIQa8fO.js.map +1 -0
  36. package/dist/assets/app/messages-BaNVb0bp.js +4 -0
  37. package/dist/assets/app/messages-BaNVb0bp.js.map +1 -0
  38. package/dist/assets/app/messages-BaizVXcF.js +4 -0
  39. package/dist/assets/app/messages-BaizVXcF.js.map +1 -0
  40. package/dist/assets/app/messages-BfoClA1Y.js +4 -0
  41. package/dist/assets/app/messages-BfoClA1Y.js.map +1 -0
  42. package/dist/assets/app/messages-BsKGDZnC.js +4 -0
  43. package/dist/assets/app/messages-BsKGDZnC.js.map +1 -0
  44. package/dist/assets/app/messages-Bu-TJhml.js +4 -0
  45. package/dist/assets/app/messages-Bu-TJhml.js.map +1 -0
  46. package/dist/assets/app/messages-BvOKnBQk.js +4 -0
  47. package/dist/assets/app/messages-BvOKnBQk.js.map +1 -0
  48. package/dist/assets/app/messages-BxDzCiWz.js +4 -0
  49. package/dist/assets/app/messages-BxDzCiWz.js.map +1 -0
  50. package/dist/assets/app/messages-CDgFOy4S.js +4 -0
  51. package/dist/assets/app/messages-CDgFOy4S.js.map +1 -0
  52. package/dist/assets/app/messages-CLbTz0o9.js +4 -0
  53. package/dist/assets/app/messages-CLbTz0o9.js.map +1 -0
  54. package/dist/assets/app/messages-CNwSh0t7.js +4 -0
  55. package/dist/assets/app/messages-CNwSh0t7.js.map +1 -0
  56. package/dist/assets/app/messages-CSMNJ6P8.js +4 -0
  57. package/dist/assets/app/messages-CSMNJ6P8.js.map +1 -0
  58. package/dist/assets/app/messages-CZQUw3mp.js +4 -0
  59. package/dist/assets/app/messages-CZQUw3mp.js.map +1 -0
  60. package/dist/assets/app/messages-CZT41oVp.js +4 -0
  61. package/dist/assets/app/messages-CZT41oVp.js.map +1 -0
  62. package/dist/assets/app/messages-C_b-d3t8.js +4 -0
  63. package/dist/assets/app/messages-C_b-d3t8.js.map +1 -0
  64. package/dist/assets/app/messages-C_u3MTc2.js +4 -0
  65. package/dist/assets/app/messages-C_u3MTc2.js.map +1 -0
  66. package/dist/assets/app/messages-Cn8nHZic.js +4 -0
  67. package/dist/assets/app/messages-Cn8nHZic.js.map +1 -0
  68. package/dist/assets/app/messages-CtDywJUm.js +4 -0
  69. package/dist/assets/app/messages-CtDywJUm.js.map +1 -0
  70. package/dist/assets/app/messages-CurtIjBF.js +4 -0
  71. package/dist/assets/app/messages-CurtIjBF.js.map +1 -0
  72. package/dist/assets/app/messages-Cv6zIbaP.js +4 -0
  73. package/dist/assets/app/messages-Cv6zIbaP.js.map +1 -0
  74. package/dist/assets/app/messages-D1eLQuPE.js +4 -0
  75. package/dist/assets/app/messages-D1eLQuPE.js.map +1 -0
  76. package/dist/assets/app/messages-D8vHEaYW.js +4 -0
  77. package/dist/assets/app/messages-D8vHEaYW.js.map +1 -0
  78. package/dist/assets/app/messages-DJ1Q4GeC.js +4 -0
  79. package/dist/assets/app/messages-DJ1Q4GeC.js.map +1 -0
  80. package/dist/assets/app/messages-DRL3exqd.js +4 -0
  81. package/dist/assets/app/messages-DRL3exqd.js.map +1 -0
  82. package/dist/assets/app/messages-DWLPQRTp.js +4 -0
  83. package/dist/assets/app/messages-DWLPQRTp.js.map +1 -0
  84. package/dist/assets/app/messages-DjVaE9YE.js +4 -0
  85. package/dist/assets/app/messages-DjVaE9YE.js.map +1 -0
  86. package/dist/assets/app/messages-DqpMfFJR.js +4 -0
  87. package/dist/assets/app/messages-DqpMfFJR.js.map +1 -0
  88. package/dist/assets/app/messages-ETjhJBEN.js +4 -0
  89. package/dist/assets/app/messages-ETjhJBEN.js.map +1 -0
  90. package/dist/assets/app/messages-EUKrgrGn.js +4 -0
  91. package/dist/assets/app/messages-EUKrgrGn.js.map +1 -0
  92. package/dist/assets/app/messages-QQrOUcPW.js +4 -0
  93. package/dist/assets/app/messages-QQrOUcPW.js.map +1 -0
  94. package/dist/assets/app/messages-e2QGqFL6.js +4 -0
  95. package/dist/assets/app/messages-e2QGqFL6.js.map +1 -0
  96. package/dist/assets/app/messages-p61py7gD.js +4 -0
  97. package/dist/assets/app/messages-p61py7gD.js.map +1 -0
  98. package/dist/assets/asset.d.ts +1 -0
  99. package/dist/assets/asset.d.ts.map +1 -1
  100. package/dist/assets/assets-middleware.d.ts.map +1 -1
  101. package/dist/assets/assets-middleware.js +12 -7
  102. package/dist/assets/assets-middleware.js.map +1 -1
  103. package/dist/assets/index.d.ts +3 -2
  104. package/dist/assets/index.d.ts.map +1 -1
  105. package/dist/assets/index.js +13 -1
  106. package/dist/assets/index.js.map +1 -1
  107. package/dist/client/client-store.d.ts +3 -3
  108. package/dist/client/client-store.d.ts.map +1 -1
  109. package/dist/client/client-store.js +6 -5
  110. package/dist/client/client-store.js.map +1 -1
  111. package/dist/device/device-manager.d.ts +12 -13
  112. package/dist/device/device-manager.d.ts.map +1 -1
  113. package/dist/device/device-manager.js +5 -3
  114. package/dist/device/device-manager.js.map +1 -1
  115. package/dist/device/device-store.d.ts +3 -3
  116. package/dist/device/device-store.d.ts.map +1 -1
  117. package/dist/device/device-store.js +10 -9
  118. package/dist/device/device-store.js.map +1 -1
  119. package/dist/dpop/dpop-manager.d.ts +15 -7
  120. package/dist/dpop/dpop-manager.d.ts.map +1 -1
  121. package/dist/dpop/dpop-manager.js +17 -3
  122. package/dist/dpop/dpop-manager.js.map +1 -1
  123. package/dist/dpop/dpop-nonce.d.ts +11 -5
  124. package/dist/dpop/dpop-nonce.d.ts.map +1 -1
  125. package/dist/dpop/dpop-nonce.js +47 -38
  126. package/dist/dpop/dpop-nonce.js.map +1 -1
  127. package/dist/errors/handle-unavailable-error.d.ts +11 -0
  128. package/dist/errors/handle-unavailable-error.d.ts.map +1 -0
  129. package/dist/errors/handle-unavailable-error.js +19 -0
  130. package/dist/errors/handle-unavailable-error.js.map +1 -0
  131. package/dist/errors/invalid-request-error.d.ts +6 -8
  132. package/dist/errors/invalid-request-error.d.ts.map +1 -1
  133. package/dist/errors/invalid-request-error.js +10 -8
  134. package/dist/errors/invalid-request-error.js.map +1 -1
  135. package/dist/lib/csp/index.d.ts +18 -0
  136. package/dist/lib/csp/index.d.ts.map +1 -0
  137. package/dist/lib/csp/index.js +72 -0
  138. package/dist/lib/csp/index.js.map +1 -0
  139. package/dist/lib/hcaptcha.d.ts +177 -0
  140. package/dist/lib/hcaptcha.d.ts.map +1 -0
  141. package/dist/lib/hcaptcha.js +155 -0
  142. package/dist/lib/hcaptcha.js.map +1 -0
  143. package/dist/lib/html/build-document.d.ts +11 -3
  144. package/dist/lib/html/build-document.d.ts.map +1 -1
  145. package/dist/lib/html/build-document.js +51 -15
  146. package/dist/lib/html/build-document.js.map +1 -1
  147. package/dist/lib/http/middleware.d.ts.map +1 -1
  148. package/dist/lib/http/middleware.js +4 -1
  149. package/dist/lib/http/middleware.js.map +1 -1
  150. package/dist/lib/http/request.d.ts +18 -3
  151. package/dist/lib/http/request.d.ts.map +1 -1
  152. package/dist/lib/http/request.js +56 -23
  153. package/dist/lib/http/request.js.map +1 -1
  154. package/dist/lib/http/response.d.ts +4 -2
  155. package/dist/lib/http/response.d.ts.map +1 -1
  156. package/dist/lib/http/response.js +23 -5
  157. package/dist/lib/http/response.js.map +1 -1
  158. package/dist/lib/locale.d.ts +15 -0
  159. package/dist/lib/locale.d.ts.map +1 -0
  160. package/dist/lib/locale.js +17 -0
  161. package/dist/lib/locale.js.map +1 -0
  162. package/dist/lib/util/function.d.ts +2 -2
  163. package/dist/lib/util/function.d.ts.map +1 -1
  164. package/dist/lib/util/function.js.map +1 -1
  165. package/dist/lib/util/type.d.ts +88 -1
  166. package/dist/lib/util/type.d.ts.map +1 -1
  167. package/dist/lib/util/type.js +41 -0
  168. package/dist/lib/util/type.js.map +1 -1
  169. package/dist/metadata/build-metadata.d.ts +2 -2
  170. package/dist/metadata/build-metadata.d.ts.map +1 -1
  171. package/dist/metadata/build-metadata.js.map +1 -1
  172. package/dist/oauth-errors.d.ts +1 -0
  173. package/dist/oauth-errors.d.ts.map +1 -1
  174. package/dist/oauth-errors.js +3 -1
  175. package/dist/oauth-errors.js.map +1 -1
  176. package/dist/oauth-hooks.d.ts +60 -3
  177. package/dist/oauth-hooks.d.ts.map +1 -1
  178. package/dist/oauth-hooks.js +3 -3
  179. package/dist/oauth-hooks.js.map +1 -1
  180. package/dist/oauth-provider.d.ts +28 -22
  181. package/dist/oauth-provider.d.ts.map +1 -1
  182. package/dist/oauth-provider.js +212 -211
  183. package/dist/oauth-provider.js.map +1 -1
  184. package/dist/oauth-verifier.d.ts +1 -1
  185. package/dist/oauth-verifier.d.ts.map +1 -1
  186. package/dist/oauth-verifier.js +2 -1
  187. package/dist/oauth-verifier.js.map +1 -1
  188. package/dist/output/build-authorize-data.d.ts +0 -1
  189. package/dist/output/build-authorize-data.d.ts.map +1 -1
  190. package/dist/output/build-authorize-data.js +0 -1
  191. package/dist/output/build-authorize-data.js.map +1 -1
  192. package/dist/output/build-customization-data.d.ts +232 -0
  193. package/dist/output/build-customization-data.d.ts.map +1 -0
  194. package/dist/output/build-customization-data.js +145 -0
  195. package/dist/output/build-customization-data.js.map +1 -0
  196. package/dist/output/output-manager.d.ts +16 -9
  197. package/dist/output/output-manager.d.ts.map +1 -1
  198. package/dist/output/output-manager.js +78 -42
  199. package/dist/output/output-manager.js.map +1 -1
  200. package/dist/output/send-authorize-redirect.d.ts +9 -6
  201. package/dist/output/send-authorize-redirect.d.ts.map +1 -1
  202. package/dist/output/send-authorize-redirect.js +20 -14
  203. package/dist/output/send-authorize-redirect.js.map +1 -1
  204. package/dist/output/send-web-page.d.ts +7 -2
  205. package/dist/output/send-web-page.d.ts.map +1 -1
  206. package/dist/output/send-web-page.js +37 -21
  207. package/dist/output/send-web-page.js.map +1 -1
  208. package/dist/request/request-manager.d.ts +1 -1
  209. package/dist/request/request-manager.d.ts.map +1 -1
  210. package/dist/request/request-manager.js +4 -4
  211. package/dist/request/request-manager.js.map +1 -1
  212. package/dist/request/request-store.d.ts +3 -3
  213. package/dist/request/request-store.d.ts.map +1 -1
  214. package/dist/request/request-store.js +11 -10
  215. package/dist/request/request-store.js.map +1 -1
  216. package/dist/token/token-store.d.ts +4 -4
  217. package/dist/token/token-store.d.ts.map +1 -1
  218. package/dist/token/token-store.js +13 -12
  219. package/dist/token/token-store.js.map +1 -1
  220. package/package.json +46 -21
  221. package/rollup.config.js +61 -17
  222. package/src/account/account-manager.ts +159 -8
  223. package/src/account/account-store.ts +127 -32
  224. package/src/account/sign-in-data.ts +15 -0
  225. package/src/account/sign-up-data.ts +11 -0
  226. package/src/assets/app/app.tsx +31 -16
  227. package/src/assets/app/backend-data.ts +15 -60
  228. package/src/assets/app/backend-types.ts +66 -0
  229. package/src/assets/app/components/forms/button-toggle-visibility.tsx +43 -0
  230. package/src/assets/app/components/forms/button.tsx +60 -0
  231. package/src/assets/app/components/forms/fieldset.tsx +55 -0
  232. package/src/assets/app/components/forms/form-card-async.tsx +103 -0
  233. package/src/assets/app/components/forms/form-card.tsx +49 -0
  234. package/src/assets/app/components/forms/input-checkbox.tsx +73 -0
  235. package/src/assets/app/components/forms/input-container.tsx +107 -0
  236. package/src/assets/app/components/forms/input-email-address.tsx +66 -0
  237. package/src/assets/app/components/forms/input-new-password.tsx +62 -0
  238. package/src/assets/app/components/forms/input-password.tsx +88 -0
  239. package/src/assets/app/components/forms/input-text.tsx +76 -0
  240. package/src/assets/app/components/forms/input-token.tsx +94 -0
  241. package/src/assets/app/components/forms/wizard-card.tsx +116 -0
  242. package/src/assets/app/components/layouts/layout-title-page.tsx +77 -0
  243. package/src/assets/app/components/layouts/layout-welcome.tsx +73 -0
  244. package/src/assets/app/components/utils/account-identifier.tsx +23 -0
  245. package/src/assets/app/components/utils/account-image.tsx +33 -0
  246. package/src/assets/app/components/utils/admonition.tsx +52 -0
  247. package/src/assets/app/components/utils/client-name.tsx +45 -0
  248. package/src/assets/app/components/utils/error-card.tsx +93 -0
  249. package/src/assets/app/components/utils/error-message.tsx +62 -0
  250. package/src/assets/app/components/utils/help-card.tsx +46 -0
  251. package/src/assets/app/components/utils/icons.tsx +88 -0
  252. package/src/assets/app/components/utils/link-anchor.tsx +28 -0
  253. package/src/assets/app/components/utils/link-title.tsx +26 -0
  254. package/src/assets/app/components/utils/multi-lang-string.tsx +56 -0
  255. package/src/assets/app/components/utils/password-strength-label.tsx +37 -0
  256. package/src/assets/app/components/utils/password-strength-meter.tsx +58 -0
  257. package/src/assets/app/components/{url-viewer.tsx → utils/url-viewer.tsx} +9 -6
  258. package/src/assets/app/hooks/use-api.ts +128 -55
  259. package/src/assets/app/hooks/use-async-action.ts +120 -0
  260. package/src/assets/app/hooks/use-browser-color-scheme.ts +31 -0
  261. package/src/assets/app/hooks/use-csrf-token.ts +1 -1
  262. package/src/assets/app/hooks/use-random-string.ts +37 -0
  263. package/src/assets/app/hooks/use-stepper.ts +87 -0
  264. package/src/assets/app/index.html +182 -0
  265. package/src/assets/app/lib/api.ts +248 -79
  266. package/src/assets/app/lib/clsx.ts +5 -8
  267. package/src/assets/app/lib/json-client.ts +94 -0
  268. package/src/assets/app/lib/password.ts +98 -0
  269. package/src/assets/app/lib/ref.ts +17 -0
  270. package/src/assets/app/locales/an/messages.po +492 -0
  271. package/src/assets/app/locales/ast/messages.po +492 -0
  272. package/src/assets/app/locales/ca/messages.po +492 -0
  273. package/src/assets/app/locales/da/messages.po +492 -0
  274. package/src/assets/app/locales/de/messages.po +492 -0
  275. package/src/assets/app/locales/el/messages.po +492 -0
  276. package/src/assets/app/locales/en/messages.po +492 -0
  277. package/src/assets/app/locales/en-GB/messages.po +492 -0
  278. package/src/assets/app/locales/es/messages.po +492 -0
  279. package/src/assets/app/locales/eu/messages.po +492 -0
  280. package/src/assets/app/locales/fi/messages.po +492 -0
  281. package/src/assets/app/locales/fr/messages.po +492 -0
  282. package/src/assets/app/locales/ga/messages.po +492 -0
  283. package/src/assets/app/locales/gl/messages.po +492 -0
  284. package/src/assets/app/locales/hi/messages.po +492 -0
  285. package/src/assets/app/locales/hu/messages.po +492 -0
  286. package/src/assets/app/locales/ia/messages.po +492 -0
  287. package/src/assets/app/locales/id/messages.po +492 -0
  288. package/src/assets/app/locales/it/messages.po +492 -0
  289. package/src/assets/app/locales/ja/messages.po +492 -0
  290. package/src/assets/app/locales/km/messages.po +492 -0
  291. package/src/assets/app/locales/ko/messages.po +492 -0
  292. package/src/assets/app/locales/load.ts +8 -0
  293. package/src/assets/app/locales/locale-context.ts +19 -0
  294. package/src/assets/app/locales/locale-provider.tsx +112 -0
  295. package/src/assets/app/locales/locale-selector.tsx +58 -0
  296. package/src/assets/app/locales/locales.ts +168 -0
  297. package/src/assets/app/locales/ne/messages.po +492 -0
  298. package/src/assets/app/locales/nl/messages.po +492 -0
  299. package/src/assets/app/locales/pl/messages.po +492 -0
  300. package/src/assets/app/locales/pt-BR/messages.po +492 -0
  301. package/src/assets/app/locales/ro/messages.po +492 -0
  302. package/src/assets/app/locales/ru/messages.po +492 -0
  303. package/src/assets/app/locales/sv/messages.po +492 -0
  304. package/src/assets/app/locales/th/messages.po +492 -0
  305. package/src/assets/app/locales/tr/messages.po +492 -0
  306. package/src/assets/app/locales/uk/messages.po +492 -0
  307. package/src/assets/app/locales/vi/messages.po +492 -0
  308. package/src/assets/app/locales/zh-CN/messages.po +492 -0
  309. package/src/assets/app/locales/zh-HK/messages.po +492 -0
  310. package/src/assets/app/locales/zh-TW/messages.po +492 -0
  311. package/src/assets/app/main.css +23 -2
  312. package/src/assets/app/main.tsx +24 -8
  313. package/src/assets/app/views/authorize/accept/accept-form.tsx +150 -0
  314. package/src/assets/app/views/authorize/accept/accept-view.tsx +70 -0
  315. package/src/assets/app/views/authorize/authorize-view.tsx +180 -0
  316. package/src/assets/app/views/authorize/reset-password/reset-password-confirm-form.tsx +88 -0
  317. package/src/assets/app/views/authorize/reset-password/reset-password-request-form.tsx +80 -0
  318. package/src/assets/app/views/authorize/reset-password/reset-password-view.tsx +127 -0
  319. package/src/assets/app/views/authorize/sign-in/sign-in-form.tsx +244 -0
  320. package/src/assets/app/views/authorize/sign-in/sign-in-picker.tsx +116 -0
  321. package/src/assets/app/views/authorize/sign-in/sign-in-view.tsx +145 -0
  322. package/src/assets/app/views/authorize/sign-up/sign-up-account-form.tsx +140 -0
  323. package/src/assets/app/views/authorize/sign-up/sign-up-disclaimer.tsx +51 -0
  324. package/src/assets/app/views/authorize/sign-up/sign-up-handle-form.tsx +289 -0
  325. package/src/assets/app/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +108 -0
  326. package/src/assets/app/views/authorize/sign-up/sign-up-view.tsx +158 -0
  327. package/src/assets/app/views/authorize/welcome/welcome-view.tsx +56 -0
  328. package/src/assets/app/views/error/error-view.tsx +31 -0
  329. package/src/assets/asset.ts +1 -0
  330. package/src/assets/assets-middleware.ts +13 -8
  331. package/src/assets/index.ts +15 -2
  332. package/src/client/client-store.ts +10 -12
  333. package/src/device/device-manager.ts +14 -15
  334. package/src/device/device-store.ts +9 -15
  335. package/src/dpop/dpop-manager.ts +20 -8
  336. package/src/dpop/dpop-nonce.ts +58 -40
  337. package/src/errors/handle-unavailable-error.ts +18 -0
  338. package/src/errors/invalid-request-error.ts +10 -8
  339. package/src/lib/csp/index.ts +98 -0
  340. package/src/lib/hcaptcha.ts +182 -0
  341. package/src/lib/html/build-document.ts +60 -16
  342. package/src/lib/http/middleware.ts +4 -3
  343. package/src/lib/http/request.ts +81 -28
  344. package/src/lib/http/response.ts +22 -9
  345. package/src/lib/locale.ts +21 -0
  346. package/src/lib/util/function.ts +0 -3
  347. package/src/lib/util/type.ts +130 -1
  348. package/src/metadata/build-metadata.ts +2 -1
  349. package/src/oauth-errors.ts +1 -0
  350. package/src/oauth-hooks.ts +69 -3
  351. package/src/oauth-provider.ts +410 -315
  352. package/src/oauth-verifier.ts +3 -1
  353. package/src/output/build-authorize-data.ts +1 -3
  354. package/src/output/build-customization-data.ts +189 -0
  355. package/src/output/output-manager.ts +111 -48
  356. package/src/output/send-authorize-redirect.ts +43 -36
  357. package/src/output/send-web-page.ts +40 -26
  358. package/src/request/request-manager.ts +4 -4
  359. package/src/request/request-store.ts +12 -16
  360. package/src/token/token-store.ts +14 -18
  361. package/tailwind.config.js +5 -0
  362. package/tsconfig.backend.tsbuildinfo +1 -1
  363. package/tsconfig.frontend.tsbuildinfo +1 -1
  364. package/tsconfig.tools.tsbuildinfo +1 -1
  365. package/vite.config.mjs +16 -0
  366. package/.postcssrc.yml +0 -3
  367. package/dist/assets/app/main.css +0 -3
  368. package/dist/assets/app/main.js +0 -20
  369. package/dist/assets/app/main.js.map +0 -1
  370. package/dist/output/customization.d.ts +0 -27
  371. package/dist/output/customization.d.ts.map +0 -1
  372. package/dist/output/customization.js +0 -88
  373. package/dist/output/customization.js.map +0 -1
  374. package/src/assets/app/components/accept-form.tsx +0 -137
  375. package/src/assets/app/components/account-identifier.tsx +0 -18
  376. package/src/assets/app/components/account-picker.tsx +0 -127
  377. package/src/assets/app/components/button.tsx +0 -34
  378. package/src/assets/app/components/client-name.tsx +0 -37
  379. package/src/assets/app/components/fieldset.tsx +0 -26
  380. package/src/assets/app/components/form-card.tsx +0 -47
  381. package/src/assets/app/components/help-card.tsx +0 -42
  382. package/src/assets/app/components/icons/alert-icon.tsx +0 -5
  383. package/src/assets/app/components/icons/at-symbol-icon.tsx +0 -5
  384. package/src/assets/app/components/icons/caret-right-icon.tsx +0 -5
  385. package/src/assets/app/components/icons/lock-icon.tsx +0 -5
  386. package/src/assets/app/components/icons/token-icon.tsx +0 -5
  387. package/src/assets/app/components/icons/util.tsx +0 -17
  388. package/src/assets/app/components/info-card.tsx +0 -45
  389. package/src/assets/app/components/input-checkbox.tsx +0 -47
  390. package/src/assets/app/components/input-container.tsx +0 -37
  391. package/src/assets/app/components/input-layout.tsx +0 -47
  392. package/src/assets/app/components/input-text.tsx +0 -69
  393. package/src/assets/app/components/layout-title-page.tsx +0 -60
  394. package/src/assets/app/components/layout-welcome.tsx +0 -74
  395. package/src/assets/app/components/sign-in-form.tsx +0 -337
  396. package/src/assets/app/components/sign-up-account-form.tsx +0 -194
  397. package/src/assets/app/components/sign-up-disclaimer.tsx +0 -44
  398. package/src/assets/app/views/accept-view.tsx +0 -55
  399. package/src/assets/app/views/authorize-view.tsx +0 -106
  400. package/src/assets/app/views/error-view.tsx +0 -36
  401. package/src/assets/app/views/sign-in-view.tsx +0 -111
  402. package/src/assets/app/views/sign-up-view.tsx +0 -86
  403. package/src/assets/app/views/welcome-view.tsx +0 -54
  404. package/src/output/customization.ts +0 -118
@@ -19,7 +19,7 @@ import type { ManifestItem } from '@atproto-labs/rollup-plugin-bundle-manifest'
19
19
  // @ts-expect-error: This file is generated at build time
20
20
  // eslint-disable-next-line import/no-unresolved
21
21
  import appBundleManifestJson from './app/bundle-manifest.json'
22
- import { Asset } from './asset'
22
+ import { Asset } from './asset.js'
23
23
 
24
24
  const appBundleManifest: Map<string, ManifestItem> = new Map(
25
25
  Object.entries(appBundleManifestJson),
@@ -27,7 +27,15 @@ const appBundleManifest: Map<string, ManifestItem> = new Map(
27
27
 
28
28
  export const ASSETS_URL_PREFIX = '/@atproto/oauth-provider/~assets/'
29
29
 
30
- export async function getAsset(inputFilename: string): Promise<Asset> {
30
+ export function* enumerateAssets(mime: string): IteratorObject<Asset, void> {
31
+ for (const [filename, manifest] of appBundleManifest) {
32
+ if (manifest.mime === mime) {
33
+ yield manifestItemToAsset(filename, manifest)
34
+ }
35
+ }
36
+ }
37
+
38
+ export function getAsset(inputFilename: string): Asset {
31
39
  const filename = posix.normalize(inputFilename)
32
40
 
33
41
  if (
@@ -41,6 +49,10 @@ export async function getAsset(inputFilename: string): Promise<Asset> {
41
49
  const manifest = appBundleManifest.get(filename)
42
50
  if (!manifest) throw new AssetNotFoundError(filename)
43
51
 
52
+ return manifestItemToAsset(filename, manifest)
53
+ }
54
+
55
+ function manifestItemToAsset(filename: string, manifest: ManifestItem): Asset {
44
56
  // When this package is used as a regular "node_modules" dependency, and gets
45
57
  // bundled by the consumer, the assets should be copied to the bundle's output
46
58
  // directory. In case the bundler does not support copying assets from the
@@ -53,6 +65,7 @@ export async function getAsset(inputFilename: string): Promise<Asset> {
53
65
  return {
54
66
  url: posix.join(ASSETS_URL_PREFIX, filename),
55
67
  type: manifest.mime,
68
+ isEntry: manifest.type === 'chunk' && manifest.isEntry,
56
69
  sha256: manifest.sha256,
57
70
  createStream: data
58
71
  ? () => Readable.from(Buffer.from(data, 'base64'))
@@ -1,5 +1,5 @@
1
1
  import { OAuthClientMetadata } from '@atproto/oauth-types'
2
- import { Awaitable } from '../lib/util/type.js'
2
+ import { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'
3
3
  import { ClientId } from './client-id.js'
4
4
 
5
5
  // Export all types needed to implement the ClientStore interface
@@ -11,15 +11,13 @@ export interface ClientStore {
11
11
  findClient(clientId: ClientId): Awaitable<OAuthClientMetadata>
12
12
  }
13
13
 
14
- export function isClientStore(
15
- implementation: Record<string, unknown> & Partial<ClientStore>,
16
- ): implementation is Record<string, unknown> & ClientStore {
17
- return typeof implementation.findClient === 'function'
18
- }
14
+ export const isClientStore = buildInterfaceChecker<ClientStore>([
15
+ 'findClient', //
16
+ ])
19
17
 
20
- export function ifClientStore(
21
- implementation?: Record<string, unknown> & Partial<ClientStore>,
22
- ): ClientStore | undefined {
18
+ export function ifClientStore<V extends Partial<ClientStore>>(
19
+ implementation?: V,
20
+ ): (V & ClientStore) | undefined {
23
21
  if (implementation && isClientStore(implementation)) {
24
22
  return implementation
25
23
  }
@@ -27,9 +25,9 @@ export function ifClientStore(
27
25
  return undefined
28
26
  }
29
27
 
30
- export function asClientStore(
31
- implementation?: Record<string, unknown> & Partial<ClientStore>,
32
- ): ClientStore {
28
+ export function asClientStore<V extends Partial<ClientStore>>(
29
+ implementation?: V,
30
+ ): V & ClientStore {
33
31
  const store = ifClientStore(implementation)
34
32
  if (store) return store
35
33
 
@@ -22,10 +22,13 @@ export const deviceManagerOptionsSchema = z.object({
22
22
  /**
23
23
  * Controls whether the IP address is read from the `X-Forwarded-For` header
24
24
  * (if `true`), or from the `req.socket.remoteAddress` property (if `false`).
25
- *
26
- * @default true // (nowadays, most requests are proxied)
27
25
  */
28
- trustProxy: z.boolean().default(true),
26
+ trustProxy: z
27
+ .function()
28
+ .args<[addr: z.ZodString, i: z.ZodNumber]>(z.string(), z.number())
29
+ .returns(z.boolean())
30
+ .optional(),
31
+
29
32
  /**
30
33
  * Amount of time (in ms) after which session IDs will be rotated
31
34
  *
@@ -88,6 +91,11 @@ export type DeviceManagerOptions = z.input<typeof deviceManagerOptionsSchema>
88
91
  const cookieValueSchema = z.tuple([deviceIdSchema, sessionIdSchema])
89
92
  type CookieValue = z.infer<typeof cookieValueSchema>
90
93
 
94
+ export type DeviceInfo = {
95
+ deviceId: DeviceId
96
+ deviceMetadata: RequestMetadata
97
+ }
98
+
91
99
  /**
92
100
  * This class provides an abstraction for keeping track of DEVICE sessions. It
93
101
  * relies on a {@link DeviceStore} to persist session data and a cookie to
@@ -107,10 +115,7 @@ export class DeviceManager {
107
115
  req: IncomingMessage,
108
116
  res: ServerResponse,
109
117
  forceRotate = false,
110
- ): Promise<{
111
- deviceId: DeviceId
112
- deviceMetadata: RequestMetadata
113
- }> {
118
+ ): Promise<DeviceInfo> {
114
119
  const cookie = await this.getCookie(req)
115
120
  if (cookie) {
116
121
  return this.refresh(
@@ -127,10 +132,7 @@ export class DeviceManager {
127
132
  private async create(
128
133
  req: IncomingMessage,
129
134
  res: ServerResponse,
130
- ): Promise<{
131
- deviceId: DeviceId
132
- deviceMetadata: RequestMetadata
133
- }> {
135
+ ): Promise<DeviceInfo> {
134
136
  const deviceMetadata = this.getRequestMetadata(req)
135
137
 
136
138
  const [deviceId, sessionId] = await Promise.all([
@@ -155,10 +157,7 @@ export class DeviceManager {
155
157
  res: ServerResponse,
156
158
  [deviceId, sessionId]: CookieValue,
157
159
  forceRotate = false,
158
- ): Promise<{
159
- deviceId: DeviceId
160
- deviceMetadata: RequestMetadata
161
- }> {
160
+ ): Promise<DeviceInfo> {
162
161
  const data = await this.store.readDevice(deviceId)
163
162
  if (!data) return this.create(req, res)
164
163
 
@@ -1,10 +1,10 @@
1
- import { Awaitable } from '../lib/util/type.js'
1
+ import { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'
2
2
  import { DeviceData } from './device-data.js'
3
3
  import { DeviceId } from './device-id.js'
4
4
 
5
5
  // Export all types needed to implement the DeviceStore interface
6
- export * from './device-id.js'
7
6
  export * from './device-data.js'
7
+ export * from './device-id.js'
8
8
  export * from './session-id.js'
9
9
 
10
10
  export interface DeviceStore {
@@ -14,20 +14,14 @@ export interface DeviceStore {
14
14
  deleteDevice(deviceId: DeviceId): Awaitable<void>
15
15
  }
16
16
 
17
- export function isDeviceStore(
18
- implementation: Record<string, unknown> & Partial<DeviceStore>,
19
- ): implementation is Record<string, unknown> & DeviceStore {
20
- return (
21
- typeof implementation.createDevice === 'function' &&
22
- typeof implementation.readDevice === 'function' &&
23
- typeof implementation.updateDevice === 'function' &&
24
- typeof implementation.deleteDevice === 'function'
25
- )
26
- }
17
+ export const isDeviceStore = buildInterfaceChecker<DeviceStore>([
18
+ 'createDevice',
19
+ 'readDevice',
20
+ 'updateDevice',
21
+ 'deleteDevice',
22
+ ])
27
23
 
28
- export function asDeviceStore(
29
- implementation?: Record<string, unknown> & Partial<DeviceStore>,
30
- ): DeviceStore {
24
+ export function asDeviceStore<V>(implementation: V): V & DeviceStore {
31
25
  if (!implementation || !isDeviceStore(implementation)) {
32
26
  throw new Error('Invalid DeviceStore implementation')
33
27
  }
@@ -1,30 +1,42 @@
1
1
  import { createHash } from 'node:crypto'
2
2
  import { EmbeddedJWK, calculateJwkThumbprint, errors, jwtVerify } from 'jose'
3
+ import { z } from 'zod'
3
4
  import { DPOP_NONCE_MAX_AGE } from '../constants.js'
4
5
  import { InvalidDpopProofError } from '../errors/invalid-dpop-proof-error.js'
5
6
  import { UseDpopNonceError } from '../errors/use-dpop-nonce-error.js'
6
- import { DpopNonce, DpopNonceInput } from './dpop-nonce.js'
7
+ import {
8
+ DpopNonce,
9
+ DpopSecret,
10
+ dpopSecretSchema,
11
+ rotationIntervalSchema,
12
+ } from './dpop-nonce.js'
7
13
 
8
14
  const { JOSEError } = errors
9
15
 
10
- export { DpopNonce, type DpopNonceInput }
11
- export type DpopManagerOptions = {
16
+ export { DpopNonce, type DpopSecret }
17
+
18
+ export const dpopManagerOptionsSchema = z.object({
12
19
  /**
13
20
  * Set this to `false` to disable the use of nonces in DPoP proofs. Set this
14
21
  * to a secret Uint8Array or hex encoded string to use a predictable seed for
15
22
  * all nonces (typically useful when multiple instances are running). Leave
16
23
  * undefined to generate a random seed at startup.
17
24
  */
18
- dpopSecret?: false | DpopNonceInput
19
- dpopStep?: number
20
- }
25
+ dpopSecret: z.union([z.literal(false), dpopSecretSchema]).optional(),
26
+ dpopRotationInterval: rotationIntervalSchema.optional(),
27
+ })
28
+ export type DpopManagerOptions = z.input<typeof dpopManagerOptionsSchema>
21
29
 
22
30
  export class DpopManager {
23
31
  protected readonly dpopNonce?: DpopNonce
24
32
 
25
- constructor({ dpopSecret, dpopStep }: DpopManagerOptions = {}) {
33
+ constructor(options: DpopManagerOptions = {}) {
34
+ const { dpopSecret, dpopRotationInterval } =
35
+ dpopManagerOptionsSchema.parse(options)
26
36
  this.dpopNonce =
27
- dpopSecret === false ? undefined : DpopNonce.from(dpopSecret, dpopStep)
37
+ dpopSecret === false
38
+ ? undefined
39
+ : new DpopNonce(dpopSecret, dpopRotationInterval)
28
40
  }
29
41
 
30
42
  nextNonce(): string | undefined {
@@ -1,48 +1,68 @@
1
1
  import { createHmac, randomBytes } from 'node:crypto'
2
+ import { z } from 'zod'
2
3
  import { DPOP_NONCE_MAX_AGE } from '../constants.js'
3
4
 
4
- function numTo64bits(num: number) {
5
- const arr = new Uint8Array(8)
6
- arr[7] = (num = num | 0) & 0xff
7
- arr[6] = (num >>= 8) & 0xff
8
- arr[5] = (num >>= 8) & 0xff
9
- arr[4] = (num >>= 8) & 0xff
10
- arr[3] = (num >>= 8) & 0xff
11
- arr[2] = (num >>= 8) & 0xff
12
- arr[1] = (num >>= 8) & 0xff
13
- arr[0] = (num >>= 8) & 0xff
14
- return arr
15
- }
5
+ const MAX_ROTATION_INTERVAL = DPOP_NONCE_MAX_AGE / 3
6
+ const MIN_ROTATION_INTERVAL = Math.min(1000, MAX_ROTATION_INTERVAL)
7
+
8
+ export const rotationIntervalSchema = z
9
+ .number()
10
+ .int()
11
+ .min(MIN_ROTATION_INTERVAL)
12
+ .max(MAX_ROTATION_INTERVAL)
13
+
14
+ const SECRET_BYTE_LENGTH = 32
16
15
 
17
- export type DpopNonceInput = string | Uint8Array | DpopNonce
16
+ export const secretBytesSchema = z
17
+ .instanceof(Uint8Array)
18
+ .refine((secret) => secret.length === SECRET_BYTE_LENGTH, {
19
+ message: `Secret must be exactly ${SECRET_BYTE_LENGTH} bytes long`,
20
+ })
21
+
22
+ export const secretHexSchema = z
23
+ .string()
24
+ .regex(
25
+ /^[0-9a-f]+$/i,
26
+ `Secret must be a ${SECRET_BYTE_LENGTH * 2} chars hex string`,
27
+ )
28
+ .length(SECRET_BYTE_LENGTH * 2)
29
+ .transform((hex): Uint8Array => Buffer.from(hex, 'hex'))
30
+
31
+ export const dpopSecretSchema = z.union([secretBytesSchema, secretHexSchema])
32
+ export type DpopSecret = z.input<typeof dpopSecretSchema>
18
33
 
19
34
  export class DpopNonce {
20
- #secret: Uint8Array
21
- #counter: number
35
+ readonly #rotationInterval: number
36
+ readonly #secret: Uint8Array
22
37
 
38
+ // Nonce state
39
+ #counter: number
23
40
  #prev: string
24
41
  #now: string
25
42
  #next: string
26
43
 
27
44
  constructor(
28
- protected readonly secret: Uint8Array,
29
- protected readonly step: number,
45
+ secret: DpopSecret = randomBytes(SECRET_BYTE_LENGTH),
46
+ rotationInterval = MAX_ROTATION_INTERVAL,
30
47
  ) {
31
- if (secret.length !== 32) throw new TypeError('Expected 32 bytes')
32
- if (this.step < 0 || this.step > DPOP_NONCE_MAX_AGE / 3) {
33
- throw new TypeError('Invalid step')
34
- }
35
-
36
- this.#secret = Uint8Array.from(secret)
37
- this.#counter = (Date.now() / step) | 0
48
+ this.#rotationInterval = rotationIntervalSchema.parse(rotationInterval)
49
+ this.#secret = Uint8Array.from(dpopSecretSchema.parse(secret))
38
50
 
51
+ this.#counter = this.currentCounter
39
52
  this.#prev = this.compute(this.#counter - 1)
40
53
  this.#now = this.compute(this.#counter)
41
54
  this.#next = this.compute(this.#counter + 1)
42
55
  }
43
56
 
57
+ /**
58
+ * Returns the number of full rotations since the epoch
59
+ */
60
+ protected get currentCounter() {
61
+ return (Date.now() / this.#rotationInterval) | 0
62
+ }
63
+
44
64
  protected rotate() {
45
- const counter = (Date.now() / this.step) | 0
65
+ const counter = this.currentCounter
46
66
  switch (counter - this.#counter) {
47
67
  case 0:
48
68
  // counter === this.#counter => nothing to do
@@ -84,20 +104,18 @@ export class DpopNonce {
84
104
  public check(nonce: string) {
85
105
  return this.#next === nonce || this.#now === nonce || this.#prev === nonce
86
106
  }
107
+ }
87
108
 
88
- static from(
89
- input: DpopNonceInput = randomBytes(32),
90
- step = DPOP_NONCE_MAX_AGE / 3,
91
- ): DpopNonce {
92
- if (input instanceof DpopNonce) {
93
- return input
94
- }
95
- if (input instanceof Uint8Array) {
96
- return new DpopNonce(input, step)
97
- }
98
- if (typeof input === 'string') {
99
- return new DpopNonce(Buffer.from(input, 'hex'), step)
100
- }
101
- return new DpopNonce(input, step)
102
- }
109
+ function numTo64bits(num: number) {
110
+ const arr = new Uint8Array(8)
111
+ // @NOTE Assigning to an uint8 will only keep the last 8 int bits
112
+ arr[7] = num |= 0
113
+ arr[6] = num >>= 8
114
+ arr[5] = num >>= 8
115
+ arr[4] = num >>= 8
116
+ arr[3] = num >>= 8
117
+ arr[2] = num >>= 8
118
+ arr[1] = num >>= 8
119
+ arr[0] = num >>= 8
120
+ return arr
103
121
  }
@@ -0,0 +1,18 @@
1
+ import { OAuthError } from './oauth-error.js'
2
+
3
+ export class HandleUnavailableError extends OAuthError {
4
+ constructor(
5
+ readonly reason: 'syntax' | 'domain' | 'slur' | 'taken',
6
+ details: string = 'That handle is not available',
7
+ cause?: unknown,
8
+ ) {
9
+ super('handle_unavailable', details, 400, cause)
10
+ }
11
+
12
+ toJSON() {
13
+ return {
14
+ ...super.toJSON(),
15
+ reason: this.reason,
16
+ } as const
17
+ }
18
+ }
@@ -2,23 +2,20 @@ import { OAuthError } from './oauth-error.js'
2
2
 
3
3
  /**
4
4
  * @see
5
- * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token }
6
- *
7
- * The request is missing a required parameter, includes an unsupported
5
+ * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 | RFC6749 - Issuing an Access Token}
6
+ * : The request is missing a required parameter, includes an unsupported
8
7
  * parameter value (other than grant type), repeats a parameter, includes
9
8
  * multiple credentials, utilizes more than one mechanism for authenticating the
10
9
  * client, or is otherwise malformed.
11
10
  *
12
11
  * @see
13
12
  * {@link https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 | RFC6749 - Authorization Code Grant, Authorization Request}
14
- *
15
- * The request is missing a required parameter, includes an invalid parameter
13
+ * : The request is missing a required parameter, includes an invalid parameter
16
14
  * value, includes a parameter more than once, or is otherwise malformed.
17
15
  *
18
16
  * @see
19
- * {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field }
20
- *
21
- * The request is missing a required parameter, includes an unsupported
17
+ * {@link https://datatracker.ietf.org/doc/html/rfc6750#section-3.1 | RFC6750 - The WWW-Authenticate Response Header Field}
18
+ * : The request is missing a required parameter, includes an unsupported
22
19
  * parameter or parameter value, repeats the same parameter, uses more than one
23
20
  * method for including an access token, or is otherwise malformed. The resource
24
21
  * server SHOULD respond with the HTTP 400 (Bad Request) status code.
@@ -27,4 +24,9 @@ export class InvalidRequestError extends OAuthError {
27
24
  constructor(error_description: string, cause?: unknown) {
28
25
  super('invalid_request', error_description, 400, cause)
29
26
  }
27
+
28
+ static from(err: unknown, message = 'Invalid request data'): OAuthError {
29
+ if (err instanceof OAuthError) return err
30
+ return new InvalidRequestError(message, err)
31
+ }
30
32
  }
@@ -0,0 +1,98 @@
1
+ import { Simplify } from '../util/type.js'
2
+
3
+ export type CspValue =
4
+ | `data:`
5
+ | `https:${string}`
6
+ | `'none'`
7
+ | `'self'`
8
+ | `'sha256-${string}'`
9
+ | `'nonce-${string}'`
10
+ | `'unsafe-inline'`
11
+ | `'unsafe-eval'`
12
+ | `'strict-dynamic'`
13
+ | `'report-sample'`
14
+ | `'unsafe-hashes'`
15
+
16
+ const STRING_DIRECTIVES = ['base-uri'] as const
17
+ const BOOLEAN_DIRECTIVES = [
18
+ 'upgrade-insecure-requests',
19
+ 'block-all-mixed-content',
20
+ ] as const
21
+ const ARRAY_DIRECTIVES = [
22
+ 'connect-src',
23
+ 'default-src',
24
+ 'form-action',
25
+ 'frame-ancestors',
26
+ 'frame-src',
27
+ 'img-src',
28
+ 'script-src',
29
+ 'style-src',
30
+ ] as const
31
+
32
+ export type CspConfig = Simplify<
33
+ {
34
+ [K in (typeof BOOLEAN_DIRECTIVES)[number]]?: boolean
35
+ } & {
36
+ [K in (typeof STRING_DIRECTIVES)[number]]?: CspValue
37
+ } & {
38
+ [K in (typeof ARRAY_DIRECTIVES)[number]]?: readonly CspValue[]
39
+ }
40
+ >
41
+
42
+ const NONE = "'none'"
43
+
44
+ export function buildCsp(config: CspConfig): string {
45
+ const values: string[] = []
46
+
47
+ for (const name of BOOLEAN_DIRECTIVES) {
48
+ if (config[name] === true) values.push(name)
49
+ }
50
+
51
+ for (const name of STRING_DIRECTIVES) {
52
+ if (config[name]) values.push(`${name} ${config[name]}`)
53
+ }
54
+
55
+ for (const name of ARRAY_DIRECTIVES) {
56
+ if (config[name]?.length) values.push(`${name} ${config[name].join(' ')}`)
57
+ }
58
+
59
+ return values.join('; ')
60
+ }
61
+
62
+ export function mergeCsp(a: CspConfig, b?: CspConfig): CspConfig
63
+ export function mergeCsp(a: CspConfig | undefined, b: CspConfig): CspConfig
64
+ export function mergeCsp(a?: CspConfig, b?: CspConfig): CspConfig | undefined
65
+ export function mergeCsp(a?: CspConfig, b?: CspConfig): CspConfig | undefined {
66
+ if (!a) return b
67
+ if (!b) return a
68
+
69
+ const result: CspConfig = {}
70
+
71
+ for (const name of BOOLEAN_DIRECTIVES) {
72
+ if (a[name] || b[name]) {
73
+ result[name] = true
74
+ }
75
+ }
76
+
77
+ for (const name of STRING_DIRECTIVES) {
78
+ if (a[name] || b[name]) {
79
+ const aNotNone = a[name] === NONE ? undefined : a[name]
80
+ const bNotNone = b[name] === NONE ? undefined : b[name]
81
+ // @NOTE b takes precedence
82
+ result[name] = bNotNone || aNotNone || NONE
83
+ }
84
+ }
85
+
86
+ for (const name of ARRAY_DIRECTIVES) {
87
+ if (a[name] && b[name]) {
88
+ const set = new Set(a[name])
89
+ if (b[name]) for (const value of b[name]) set.add(value)
90
+ if (set.size > 1 && set.has(NONE)) set.delete(NONE)
91
+ result[name] = [...set]
92
+ } else if (a[name] || b[name]) {
93
+ result[name] = Array.from((a[name] || b[name])!)
94
+ }
95
+ }
96
+
97
+ return result
98
+ }