@atproto/oauth-provider 0.4.0 → 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 (402) hide show
  1. package/.linguirc +57 -0
  2. package/CHANGELOG.md +21 -0
  3. package/dist/account/account-manager.d.ts +17 -3
  4. package/dist/account/account-manager.d.ts.map +1 -1
  5. package/dist/account/account-manager.js +102 -8
  6. package/dist/account/account-manager.js.map +1 -1
  7. package/dist/account/account-store.d.ts +81 -15
  8. package/dist/account/account-store.d.ts.map +1 -1
  9. package/dist/account/account-store.js +70 -19
  10. package/dist/account/account-store.js.map +1 -1
  11. package/dist/account/sign-in-data.d.ts +28 -0
  12. package/dist/account/sign-in-data.d.ts.map +1 -0
  13. package/dist/account/sign-in-data.js +16 -0
  14. package/dist/account/sign-in-data.js.map +1 -0
  15. package/dist/account/sign-up-data.d.ts +26 -0
  16. package/dist/account/sign-up-data.d.ts.map +1 -0
  17. package/dist/account/sign-up-data.js +11 -0
  18. package/dist/account/sign-up-data.js.map +1 -0
  19. package/dist/assets/app/bundle-manifest.json +598 -6
  20. package/dist/assets/app/index-ItwwtJ8r.js +36 -0
  21. package/dist/assets/app/index-ItwwtJ8r.js.map +1 -0
  22. package/dist/assets/app/main-B_dNxQo_.js +4 -0
  23. package/dist/assets/app/main-B_dNxQo_.js.map +1 -0
  24. package/dist/assets/app/main-CSatvmRR.css +3 -0
  25. package/dist/assets/app/main-CSatvmRR.js +306 -0
  26. package/dist/assets/app/main-CSatvmRR.js.map +1 -0
  27. package/dist/assets/app/messages-BQeltXSF.js +4 -0
  28. package/dist/assets/app/messages-BQeltXSF.js.map +1 -0
  29. package/dist/assets/app/messages-BQkEhfjg.js +4 -0
  30. package/dist/assets/app/messages-BQkEhfjg.js.map +1 -0
  31. package/dist/assets/app/messages-BUjKj_UJ.js +4 -0
  32. package/dist/assets/app/messages-BUjKj_UJ.js.map +1 -0
  33. package/dist/assets/app/messages-BWIQa8fO.js +4 -0
  34. package/dist/assets/app/messages-BWIQa8fO.js.map +1 -0
  35. package/dist/assets/app/messages-BaNVb0bp.js +4 -0
  36. package/dist/assets/app/messages-BaNVb0bp.js.map +1 -0
  37. package/dist/assets/app/messages-BaizVXcF.js +4 -0
  38. package/dist/assets/app/messages-BaizVXcF.js.map +1 -0
  39. package/dist/assets/app/messages-BfoClA1Y.js +4 -0
  40. package/dist/assets/app/messages-BfoClA1Y.js.map +1 -0
  41. package/dist/assets/app/messages-BsKGDZnC.js +4 -0
  42. package/dist/assets/app/messages-BsKGDZnC.js.map +1 -0
  43. package/dist/assets/app/messages-Bu-TJhml.js +4 -0
  44. package/dist/assets/app/messages-Bu-TJhml.js.map +1 -0
  45. package/dist/assets/app/messages-BvOKnBQk.js +4 -0
  46. package/dist/assets/app/messages-BvOKnBQk.js.map +1 -0
  47. package/dist/assets/app/messages-BxDzCiWz.js +4 -0
  48. package/dist/assets/app/messages-BxDzCiWz.js.map +1 -0
  49. package/dist/assets/app/messages-CDgFOy4S.js +4 -0
  50. package/dist/assets/app/messages-CDgFOy4S.js.map +1 -0
  51. package/dist/assets/app/messages-CLbTz0o9.js +4 -0
  52. package/dist/assets/app/messages-CLbTz0o9.js.map +1 -0
  53. package/dist/assets/app/messages-CNwSh0t7.js +4 -0
  54. package/dist/assets/app/messages-CNwSh0t7.js.map +1 -0
  55. package/dist/assets/app/messages-CSMNJ6P8.js +4 -0
  56. package/dist/assets/app/messages-CSMNJ6P8.js.map +1 -0
  57. package/dist/assets/app/messages-CZQUw3mp.js +4 -0
  58. package/dist/assets/app/messages-CZQUw3mp.js.map +1 -0
  59. package/dist/assets/app/messages-CZT41oVp.js +4 -0
  60. package/dist/assets/app/messages-CZT41oVp.js.map +1 -0
  61. package/dist/assets/app/messages-C_b-d3t8.js +4 -0
  62. package/dist/assets/app/messages-C_b-d3t8.js.map +1 -0
  63. package/dist/assets/app/messages-C_u3MTc2.js +4 -0
  64. package/dist/assets/app/messages-C_u3MTc2.js.map +1 -0
  65. package/dist/assets/app/messages-Cn8nHZic.js +4 -0
  66. package/dist/assets/app/messages-Cn8nHZic.js.map +1 -0
  67. package/dist/assets/app/messages-CtDywJUm.js +4 -0
  68. package/dist/assets/app/messages-CtDywJUm.js.map +1 -0
  69. package/dist/assets/app/messages-CurtIjBF.js +4 -0
  70. package/dist/assets/app/messages-CurtIjBF.js.map +1 -0
  71. package/dist/assets/app/messages-Cv6zIbaP.js +4 -0
  72. package/dist/assets/app/messages-Cv6zIbaP.js.map +1 -0
  73. package/dist/assets/app/messages-D1eLQuPE.js +4 -0
  74. package/dist/assets/app/messages-D1eLQuPE.js.map +1 -0
  75. package/dist/assets/app/messages-D8vHEaYW.js +4 -0
  76. package/dist/assets/app/messages-D8vHEaYW.js.map +1 -0
  77. package/dist/assets/app/messages-DJ1Q4GeC.js +4 -0
  78. package/dist/assets/app/messages-DJ1Q4GeC.js.map +1 -0
  79. package/dist/assets/app/messages-DRL3exqd.js +4 -0
  80. package/dist/assets/app/messages-DRL3exqd.js.map +1 -0
  81. package/dist/assets/app/messages-DWLPQRTp.js +4 -0
  82. package/dist/assets/app/messages-DWLPQRTp.js.map +1 -0
  83. package/dist/assets/app/messages-DjVaE9YE.js +4 -0
  84. package/dist/assets/app/messages-DjVaE9YE.js.map +1 -0
  85. package/dist/assets/app/messages-DqpMfFJR.js +4 -0
  86. package/dist/assets/app/messages-DqpMfFJR.js.map +1 -0
  87. package/dist/assets/app/messages-ETjhJBEN.js +4 -0
  88. package/dist/assets/app/messages-ETjhJBEN.js.map +1 -0
  89. package/dist/assets/app/messages-EUKrgrGn.js +4 -0
  90. package/dist/assets/app/messages-EUKrgrGn.js.map +1 -0
  91. package/dist/assets/app/messages-QQrOUcPW.js +4 -0
  92. package/dist/assets/app/messages-QQrOUcPW.js.map +1 -0
  93. package/dist/assets/app/messages-e2QGqFL6.js +4 -0
  94. package/dist/assets/app/messages-e2QGqFL6.js.map +1 -0
  95. package/dist/assets/app/messages-p61py7gD.js +4 -0
  96. package/dist/assets/app/messages-p61py7gD.js.map +1 -0
  97. package/dist/assets/asset.d.ts +1 -0
  98. package/dist/assets/asset.d.ts.map +1 -1
  99. package/dist/assets/assets-middleware.d.ts.map +1 -1
  100. package/dist/assets/assets-middleware.js +12 -7
  101. package/dist/assets/assets-middleware.js.map +1 -1
  102. package/dist/assets/index.d.ts +3 -2
  103. package/dist/assets/index.d.ts.map +1 -1
  104. package/dist/assets/index.js +13 -1
  105. package/dist/assets/index.js.map +1 -1
  106. package/dist/client/client-store.d.ts +3 -3
  107. package/dist/client/client-store.d.ts.map +1 -1
  108. package/dist/client/client-store.js +6 -5
  109. package/dist/client/client-store.js.map +1 -1
  110. package/dist/device/device-manager.d.ts +9 -8
  111. package/dist/device/device-manager.d.ts.map +1 -1
  112. package/dist/device/device-manager.js.map +1 -1
  113. package/dist/device/device-store.d.ts +3 -3
  114. package/dist/device/device-store.d.ts.map +1 -1
  115. package/dist/device/device-store.js +10 -9
  116. package/dist/device/device-store.js.map +1 -1
  117. package/dist/dpop/dpop-manager.d.ts +15 -7
  118. package/dist/dpop/dpop-manager.d.ts.map +1 -1
  119. package/dist/dpop/dpop-manager.js +17 -3
  120. package/dist/dpop/dpop-manager.js.map +1 -1
  121. package/dist/dpop/dpop-nonce.d.ts +11 -5
  122. package/dist/dpop/dpop-nonce.d.ts.map +1 -1
  123. package/dist/dpop/dpop-nonce.js +47 -38
  124. package/dist/dpop/dpop-nonce.js.map +1 -1
  125. package/dist/errors/handle-unavailable-error.d.ts +11 -0
  126. package/dist/errors/handle-unavailable-error.d.ts.map +1 -0
  127. package/dist/errors/handle-unavailable-error.js +19 -0
  128. package/dist/errors/handle-unavailable-error.js.map +1 -0
  129. package/dist/errors/invalid-request-error.d.ts +6 -8
  130. package/dist/errors/invalid-request-error.d.ts.map +1 -1
  131. package/dist/errors/invalid-request-error.js +10 -8
  132. package/dist/errors/invalid-request-error.js.map +1 -1
  133. package/dist/lib/csp/index.d.ts +18 -0
  134. package/dist/lib/csp/index.d.ts.map +1 -0
  135. package/dist/lib/csp/index.js +72 -0
  136. package/dist/lib/csp/index.js.map +1 -0
  137. package/dist/lib/hcaptcha.d.ts +177 -0
  138. package/dist/lib/hcaptcha.d.ts.map +1 -0
  139. package/dist/lib/hcaptcha.js +155 -0
  140. package/dist/lib/hcaptcha.js.map +1 -0
  141. package/dist/lib/html/build-document.d.ts +11 -3
  142. package/dist/lib/html/build-document.d.ts.map +1 -1
  143. package/dist/lib/html/build-document.js +51 -15
  144. package/dist/lib/html/build-document.js.map +1 -1
  145. package/dist/lib/http/middleware.d.ts.map +1 -1
  146. package/dist/lib/http/middleware.js +4 -1
  147. package/dist/lib/http/middleware.js.map +1 -1
  148. package/dist/lib/http/request.d.ts +5 -2
  149. package/dist/lib/http/request.d.ts.map +1 -1
  150. package/dist/lib/http/request.js +16 -1
  151. package/dist/lib/http/request.js.map +1 -1
  152. package/dist/lib/http/response.d.ts +4 -2
  153. package/dist/lib/http/response.d.ts.map +1 -1
  154. package/dist/lib/http/response.js +23 -5
  155. package/dist/lib/http/response.js.map +1 -1
  156. package/dist/lib/locale.d.ts +15 -0
  157. package/dist/lib/locale.d.ts.map +1 -0
  158. package/dist/lib/locale.js +17 -0
  159. package/dist/lib/locale.js.map +1 -0
  160. package/dist/lib/util/function.d.ts +2 -2
  161. package/dist/lib/util/function.d.ts.map +1 -1
  162. package/dist/lib/util/function.js.map +1 -1
  163. package/dist/lib/util/type.d.ts +88 -1
  164. package/dist/lib/util/type.d.ts.map +1 -1
  165. package/dist/lib/util/type.js +41 -0
  166. package/dist/lib/util/type.js.map +1 -1
  167. package/dist/metadata/build-metadata.d.ts +2 -2
  168. package/dist/metadata/build-metadata.d.ts.map +1 -1
  169. package/dist/metadata/build-metadata.js.map +1 -1
  170. package/dist/oauth-errors.d.ts +1 -0
  171. package/dist/oauth-errors.d.ts.map +1 -1
  172. package/dist/oauth-errors.js +3 -1
  173. package/dist/oauth-errors.js.map +1 -1
  174. package/dist/oauth-hooks.d.ts +60 -3
  175. package/dist/oauth-hooks.d.ts.map +1 -1
  176. package/dist/oauth-hooks.js +3 -3
  177. package/dist/oauth-hooks.js.map +1 -1
  178. package/dist/oauth-provider.d.ts +23 -18
  179. package/dist/oauth-provider.d.ts.map +1 -1
  180. package/dist/oauth-provider.js +207 -204
  181. package/dist/oauth-provider.js.map +1 -1
  182. package/dist/oauth-verifier.d.ts +1 -1
  183. package/dist/oauth-verifier.d.ts.map +1 -1
  184. package/dist/oauth-verifier.js +2 -1
  185. package/dist/oauth-verifier.js.map +1 -1
  186. package/dist/output/build-authorize-data.d.ts +0 -1
  187. package/dist/output/build-authorize-data.d.ts.map +1 -1
  188. package/dist/output/build-authorize-data.js +0 -1
  189. package/dist/output/build-authorize-data.js.map +1 -1
  190. package/dist/output/build-customization-data.d.ts +232 -0
  191. package/dist/output/build-customization-data.d.ts.map +1 -0
  192. package/dist/output/build-customization-data.js +145 -0
  193. package/dist/output/build-customization-data.js.map +1 -0
  194. package/dist/output/output-manager.d.ts +16 -9
  195. package/dist/output/output-manager.d.ts.map +1 -1
  196. package/dist/output/output-manager.js +78 -42
  197. package/dist/output/output-manager.js.map +1 -1
  198. package/dist/output/send-authorize-redirect.d.ts +9 -6
  199. package/dist/output/send-authorize-redirect.d.ts.map +1 -1
  200. package/dist/output/send-authorize-redirect.js +20 -14
  201. package/dist/output/send-authorize-redirect.js.map +1 -1
  202. package/dist/output/send-web-page.d.ts +7 -2
  203. package/dist/output/send-web-page.d.ts.map +1 -1
  204. package/dist/output/send-web-page.js +37 -21
  205. package/dist/output/send-web-page.js.map +1 -1
  206. package/dist/request/request-manager.d.ts +1 -1
  207. package/dist/request/request-manager.d.ts.map +1 -1
  208. package/dist/request/request-manager.js +4 -4
  209. package/dist/request/request-manager.js.map +1 -1
  210. package/dist/request/request-store.d.ts +3 -3
  211. package/dist/request/request-store.d.ts.map +1 -1
  212. package/dist/request/request-store.js +11 -10
  213. package/dist/request/request-store.js.map +1 -1
  214. package/dist/token/token-store.d.ts +4 -4
  215. package/dist/token/token-store.d.ts.map +1 -1
  216. package/dist/token/token-store.js +13 -12
  217. package/dist/token/token-store.js.map +1 -1
  218. package/package.json +43 -20
  219. package/rollup.config.js +61 -17
  220. package/src/account/account-manager.ts +159 -8
  221. package/src/account/account-store.ts +127 -32
  222. package/src/account/sign-in-data.ts +15 -0
  223. package/src/account/sign-up-data.ts +11 -0
  224. package/src/assets/app/app.tsx +31 -16
  225. package/src/assets/app/backend-data.ts +15 -60
  226. package/src/assets/app/backend-types.ts +66 -0
  227. package/src/assets/app/components/forms/button-toggle-visibility.tsx +43 -0
  228. package/src/assets/app/components/forms/button.tsx +60 -0
  229. package/src/assets/app/components/forms/fieldset.tsx +55 -0
  230. package/src/assets/app/components/forms/form-card-async.tsx +103 -0
  231. package/src/assets/app/components/forms/form-card.tsx +49 -0
  232. package/src/assets/app/components/forms/input-checkbox.tsx +73 -0
  233. package/src/assets/app/components/forms/input-container.tsx +107 -0
  234. package/src/assets/app/components/forms/input-email-address.tsx +66 -0
  235. package/src/assets/app/components/forms/input-new-password.tsx +62 -0
  236. package/src/assets/app/components/forms/input-password.tsx +88 -0
  237. package/src/assets/app/components/forms/input-text.tsx +76 -0
  238. package/src/assets/app/components/forms/input-token.tsx +94 -0
  239. package/src/assets/app/components/forms/wizard-card.tsx +116 -0
  240. package/src/assets/app/components/layouts/layout-title-page.tsx +77 -0
  241. package/src/assets/app/components/layouts/layout-welcome.tsx +73 -0
  242. package/src/assets/app/components/utils/account-identifier.tsx +23 -0
  243. package/src/assets/app/components/utils/account-image.tsx +33 -0
  244. package/src/assets/app/components/utils/admonition.tsx +52 -0
  245. package/src/assets/app/components/utils/client-name.tsx +45 -0
  246. package/src/assets/app/components/utils/error-card.tsx +93 -0
  247. package/src/assets/app/components/utils/error-message.tsx +62 -0
  248. package/src/assets/app/components/utils/help-card.tsx +46 -0
  249. package/src/assets/app/components/utils/icons.tsx +88 -0
  250. package/src/assets/app/components/utils/link-anchor.tsx +28 -0
  251. package/src/assets/app/components/utils/link-title.tsx +26 -0
  252. package/src/assets/app/components/utils/multi-lang-string.tsx +56 -0
  253. package/src/assets/app/components/utils/password-strength-label.tsx +37 -0
  254. package/src/assets/app/components/utils/password-strength-meter.tsx +58 -0
  255. package/src/assets/app/components/{url-viewer.tsx → utils/url-viewer.tsx} +9 -6
  256. package/src/assets/app/hooks/use-api.ts +128 -55
  257. package/src/assets/app/hooks/use-async-action.ts +120 -0
  258. package/src/assets/app/hooks/use-browser-color-scheme.ts +31 -0
  259. package/src/assets/app/hooks/use-csrf-token.ts +1 -1
  260. package/src/assets/app/hooks/use-random-string.ts +37 -0
  261. package/src/assets/app/hooks/use-stepper.ts +87 -0
  262. package/src/assets/app/index.html +182 -0
  263. package/src/assets/app/lib/api.ts +248 -79
  264. package/src/assets/app/lib/clsx.ts +5 -8
  265. package/src/assets/app/lib/json-client.ts +94 -0
  266. package/src/assets/app/lib/password.ts +98 -0
  267. package/src/assets/app/lib/ref.ts +17 -0
  268. package/src/assets/app/locales/an/messages.po +492 -0
  269. package/src/assets/app/locales/ast/messages.po +492 -0
  270. package/src/assets/app/locales/ca/messages.po +492 -0
  271. package/src/assets/app/locales/da/messages.po +492 -0
  272. package/src/assets/app/locales/de/messages.po +492 -0
  273. package/src/assets/app/locales/el/messages.po +492 -0
  274. package/src/assets/app/locales/en/messages.po +492 -0
  275. package/src/assets/app/locales/en-GB/messages.po +492 -0
  276. package/src/assets/app/locales/es/messages.po +492 -0
  277. package/src/assets/app/locales/eu/messages.po +492 -0
  278. package/src/assets/app/locales/fi/messages.po +492 -0
  279. package/src/assets/app/locales/fr/messages.po +492 -0
  280. package/src/assets/app/locales/ga/messages.po +492 -0
  281. package/src/assets/app/locales/gl/messages.po +492 -0
  282. package/src/assets/app/locales/hi/messages.po +492 -0
  283. package/src/assets/app/locales/hu/messages.po +492 -0
  284. package/src/assets/app/locales/ia/messages.po +492 -0
  285. package/src/assets/app/locales/id/messages.po +492 -0
  286. package/src/assets/app/locales/it/messages.po +492 -0
  287. package/src/assets/app/locales/ja/messages.po +492 -0
  288. package/src/assets/app/locales/km/messages.po +492 -0
  289. package/src/assets/app/locales/ko/messages.po +492 -0
  290. package/src/assets/app/locales/load.ts +8 -0
  291. package/src/assets/app/locales/locale-context.ts +19 -0
  292. package/src/assets/app/locales/locale-provider.tsx +112 -0
  293. package/src/assets/app/locales/locale-selector.tsx +58 -0
  294. package/src/assets/app/locales/locales.ts +168 -0
  295. package/src/assets/app/locales/ne/messages.po +492 -0
  296. package/src/assets/app/locales/nl/messages.po +492 -0
  297. package/src/assets/app/locales/pl/messages.po +492 -0
  298. package/src/assets/app/locales/pt-BR/messages.po +492 -0
  299. package/src/assets/app/locales/ro/messages.po +492 -0
  300. package/src/assets/app/locales/ru/messages.po +492 -0
  301. package/src/assets/app/locales/sv/messages.po +492 -0
  302. package/src/assets/app/locales/th/messages.po +492 -0
  303. package/src/assets/app/locales/tr/messages.po +492 -0
  304. package/src/assets/app/locales/uk/messages.po +492 -0
  305. package/src/assets/app/locales/vi/messages.po +492 -0
  306. package/src/assets/app/locales/zh-CN/messages.po +492 -0
  307. package/src/assets/app/locales/zh-HK/messages.po +492 -0
  308. package/src/assets/app/locales/zh-TW/messages.po +492 -0
  309. package/src/assets/app/main.css +23 -2
  310. package/src/assets/app/main.tsx +24 -8
  311. package/src/assets/app/views/authorize/accept/accept-form.tsx +150 -0
  312. package/src/assets/app/views/authorize/accept/accept-view.tsx +70 -0
  313. package/src/assets/app/views/authorize/authorize-view.tsx +180 -0
  314. package/src/assets/app/views/authorize/reset-password/reset-password-confirm-form.tsx +88 -0
  315. package/src/assets/app/views/authorize/reset-password/reset-password-request-form.tsx +80 -0
  316. package/src/assets/app/views/authorize/reset-password/reset-password-view.tsx +127 -0
  317. package/src/assets/app/views/authorize/sign-in/sign-in-form.tsx +244 -0
  318. package/src/assets/app/views/authorize/sign-in/sign-in-picker.tsx +116 -0
  319. package/src/assets/app/views/authorize/sign-in/sign-in-view.tsx +145 -0
  320. package/src/assets/app/views/authorize/sign-up/sign-up-account-form.tsx +140 -0
  321. package/src/assets/app/views/authorize/sign-up/sign-up-disclaimer.tsx +51 -0
  322. package/src/assets/app/views/authorize/sign-up/sign-up-handle-form.tsx +289 -0
  323. package/src/assets/app/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +108 -0
  324. package/src/assets/app/views/authorize/sign-up/sign-up-view.tsx +158 -0
  325. package/src/assets/app/views/authorize/welcome/welcome-view.tsx +56 -0
  326. package/src/assets/app/views/error/error-view.tsx +31 -0
  327. package/src/assets/asset.ts +1 -0
  328. package/src/assets/assets-middleware.ts +13 -8
  329. package/src/assets/index.ts +15 -2
  330. package/src/client/client-store.ts +10 -12
  331. package/src/device/device-manager.ts +8 -12
  332. package/src/device/device-store.ts +9 -15
  333. package/src/dpop/dpop-manager.ts +20 -8
  334. package/src/dpop/dpop-nonce.ts +58 -40
  335. package/src/errors/handle-unavailable-error.ts +18 -0
  336. package/src/errors/invalid-request-error.ts +10 -8
  337. package/src/lib/csp/index.ts +98 -0
  338. package/src/lib/hcaptcha.ts +182 -0
  339. package/src/lib/html/build-document.ts +60 -16
  340. package/src/lib/http/middleware.ts +4 -3
  341. package/src/lib/http/request.ts +31 -1
  342. package/src/lib/http/response.ts +22 -9
  343. package/src/lib/locale.ts +21 -0
  344. package/src/lib/util/function.ts +0 -3
  345. package/src/lib/util/type.ts +130 -1
  346. package/src/metadata/build-metadata.ts +2 -1
  347. package/src/oauth-errors.ts +1 -0
  348. package/src/oauth-hooks.ts +69 -3
  349. package/src/oauth-provider.ts +399 -307
  350. package/src/oauth-verifier.ts +3 -1
  351. package/src/output/build-authorize-data.ts +1 -3
  352. package/src/output/build-customization-data.ts +189 -0
  353. package/src/output/output-manager.ts +111 -48
  354. package/src/output/send-authorize-redirect.ts +43 -36
  355. package/src/output/send-web-page.ts +40 -26
  356. package/src/request/request-manager.ts +4 -4
  357. package/src/request/request-store.ts +12 -16
  358. package/src/token/token-store.ts +14 -18
  359. package/tailwind.config.js +5 -0
  360. package/tsconfig.backend.tsbuildinfo +1 -1
  361. package/tsconfig.frontend.tsbuildinfo +1 -1
  362. package/tsconfig.tools.tsbuildinfo +1 -1
  363. package/vite.config.mjs +16 -0
  364. package/.postcssrc.yml +0 -3
  365. package/dist/assets/app/main.css +0 -3
  366. package/dist/assets/app/main.js +0 -20
  367. package/dist/assets/app/main.js.map +0 -1
  368. package/dist/output/customization.d.ts +0 -27
  369. package/dist/output/customization.d.ts.map +0 -1
  370. package/dist/output/customization.js +0 -88
  371. package/dist/output/customization.js.map +0 -1
  372. package/src/assets/app/components/accept-form.tsx +0 -137
  373. package/src/assets/app/components/account-identifier.tsx +0 -18
  374. package/src/assets/app/components/account-picker.tsx +0 -127
  375. package/src/assets/app/components/button.tsx +0 -34
  376. package/src/assets/app/components/client-name.tsx +0 -37
  377. package/src/assets/app/components/fieldset.tsx +0 -26
  378. package/src/assets/app/components/form-card.tsx +0 -47
  379. package/src/assets/app/components/help-card.tsx +0 -42
  380. package/src/assets/app/components/icons/alert-icon.tsx +0 -5
  381. package/src/assets/app/components/icons/at-symbol-icon.tsx +0 -5
  382. package/src/assets/app/components/icons/caret-right-icon.tsx +0 -5
  383. package/src/assets/app/components/icons/lock-icon.tsx +0 -5
  384. package/src/assets/app/components/icons/token-icon.tsx +0 -5
  385. package/src/assets/app/components/icons/util.tsx +0 -17
  386. package/src/assets/app/components/info-card.tsx +0 -45
  387. package/src/assets/app/components/input-checkbox.tsx +0 -47
  388. package/src/assets/app/components/input-container.tsx +0 -37
  389. package/src/assets/app/components/input-layout.tsx +0 -47
  390. package/src/assets/app/components/input-text.tsx +0 -69
  391. package/src/assets/app/components/layout-title-page.tsx +0 -60
  392. package/src/assets/app/components/layout-welcome.tsx +0 -74
  393. package/src/assets/app/components/sign-in-form.tsx +0 -337
  394. package/src/assets/app/components/sign-up-account-form.tsx +0 -194
  395. package/src/assets/app/components/sign-up-disclaimer.tsx +0 -44
  396. package/src/assets/app/views/accept-view.tsx +0 -55
  397. package/src/assets/app/views/authorize-view.tsx +0 -106
  398. package/src/assets/app/views/error-view.tsx +0 -36
  399. package/src/assets/app/views/sign-in-view.tsx +0 -111
  400. package/src/assets/app/views/sign-up-view.tsx +0 -86
  401. package/src/assets/app/views/welcome-view.tsx +0 -54
  402. package/src/output/customization.ts +0 -118
@@ -0,0 +1,182 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { z } from 'zod'
3
+ import {
4
+ Fetch,
5
+ FetchBound,
6
+ bindFetch,
7
+ fetchJsonProcessor,
8
+ fetchJsonZodProcessor,
9
+ fetchOkProcessor,
10
+ } from '@atproto-labs/fetch'
11
+ import { pipe } from '@atproto-labs/pipe'
12
+
13
+ export const hcaptchaTokenSchema = z.string().min(1)
14
+ export type HcaptchaToken = z.infer<typeof hcaptchaTokenSchema>
15
+
16
+ export const hcaptchaConfigSchema = z.object({
17
+ /**
18
+ * The hCaptcha site key to use for the sign-up form.
19
+ */
20
+ siteKey: z.string().min(1),
21
+ /**
22
+ * The hCaptcha secret key to use for the sign-up form.
23
+ */
24
+ secretKey: z.string().min(1),
25
+ /**
26
+ * A salt to use when hashing client tokens.
27
+ */
28
+ tokenSalt: z.string().min(1),
29
+ /**
30
+ * The risk score over which the user is considered a threat and will be
31
+ * denied access. This will be ignored if the enterprise features are not
32
+ * available.
33
+ */
34
+ scoreThreshold: z.number().optional(),
35
+ })
36
+ export type HcaptchaConfig = z.infer<typeof hcaptchaConfigSchema>
37
+
38
+ /**
39
+ * @see {@link https://docs.hcaptcha.com/#verify-the-user-response-server-side hCaptcha API}
40
+ */
41
+ export const hcaptchaVerifyResultSchema = z.object({
42
+ /**
43
+ * is the passcode valid, and does it meet security criteria you specified, e.g. sitekey?
44
+ */
45
+ success: z.boolean(),
46
+ /**
47
+ * timestamp of the challenge (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
48
+ */
49
+ challenge_ts: z.string(),
50
+ /**
51
+ * the hostname of the site where the challenge was passed
52
+ */
53
+ hostname: z.string(),
54
+ /**
55
+ * optional: any error codes
56
+ */
57
+ 'error-codes': z.array(z.string()),
58
+ /**
59
+ * ENTERPRISE feature: a score denoting malicious activity. Value ranges from
60
+ * 0.0 (no risk) to 1.0 (confirmed threat).
61
+ */
62
+ score: z.number().optional(),
63
+ /**
64
+ * ENTERPRISE feature: reason(s) for score.
65
+ */
66
+ score_reason: z.array(z.string()).optional(),
67
+ /**
68
+ * sitekey of the request
69
+ */
70
+ sitekey: z.string().optional(),
71
+ /**
72
+ * obj of form: {'ip_device': 1, .. etc}
73
+ */
74
+ behavior_counts: z.record(z.unknown()).optional(),
75
+ /**
76
+ * how similar is this? (0.0 - 1.0, -1 on err)
77
+ */
78
+ similarity: z.number().optional(),
79
+ /**
80
+ * count of similar_tokens not processed
81
+ */
82
+ similarity_failures: z.number().optional(),
83
+ /**
84
+ * array of strings for any similarity errors
85
+ */
86
+ similarity_error_details: z.array(z.string()).optional(),
87
+ /**
88
+ * encoded clientID
89
+ */
90
+ scoped_uid_0: z.string().optional(),
91
+ /**
92
+ * encoded IP
93
+ */
94
+ scoped_uid_1: z.string().optional(),
95
+ /**
96
+ * encoded IP (APT)
97
+ */
98
+ scoped_uid_2: z.string().optional(),
99
+ /**
100
+ * Risk Insights (APT + RI)
101
+ */
102
+ risk_insights: z.record(z.unknown()).optional(),
103
+ /**
104
+ * Advanced Threat Signatures (APT)
105
+ */
106
+ sigs: z.record(z.unknown()).optional(),
107
+ /**
108
+ * tags added via Rules
109
+ */
110
+ tags: z.array(z.string()).optional(),
111
+ })
112
+
113
+ export type HcaptchaVerifyResult = z.infer<typeof hcaptchaVerifyResultSchema>
114
+
115
+ const fetchSuccessHandler = pipe(
116
+ fetchOkProcessor(),
117
+ fetchJsonProcessor(),
118
+ fetchJsonZodProcessor(hcaptchaVerifyResultSchema),
119
+ )
120
+
121
+ export class HCaptchaClient {
122
+ protected readonly fetch: FetchBound
123
+ constructor(
124
+ private readonly hostname: string,
125
+ private readonly config: HcaptchaConfig,
126
+ fetch: Fetch = globalThis.fetch,
127
+ ) {
128
+ this.fetch = bindFetch(fetch)
129
+ }
130
+
131
+ async verify(
132
+ behaviorType: 'login' | 'signup',
133
+ response: string,
134
+ remoteip: string,
135
+ handle: string,
136
+ userAgent?: string,
137
+ ) {
138
+ const result = await this.fetch('https://api.hcaptcha.com/siteverify', {
139
+ method: 'POST',
140
+ headers: {
141
+ 'Content-Type': 'application/x-www-form-urlencoded',
142
+ },
143
+ body: new URLSearchParams({
144
+ secret: this.config.secretKey,
145
+ sitekey: this.config.siteKey,
146
+ behavior_type: behaviorType,
147
+ response,
148
+ remoteip,
149
+ client_tokens: JSON.stringify({
150
+ hashedIp: this.hashToken(remoteip),
151
+ hashedHandle: this.hashToken(handle),
152
+ hashedUserAgent: userAgent ? this.hashToken(userAgent) : undefined,
153
+ }),
154
+ }).toString(),
155
+ }).then(fetchSuccessHandler)
156
+
157
+ return {
158
+ allowed: this.isAllowed(result),
159
+ result,
160
+ }
161
+ }
162
+
163
+ isAllowed({ success, hostname, score }: HcaptchaVerifyResult) {
164
+ return (
165
+ success &&
166
+ // Fool-proofing: If this is false, the user is trying to use a token
167
+ // generated for the same siteKey, but on another domain.
168
+ hostname === this.hostname &&
169
+ // Ignore if enterprise feature is not enabled
170
+ score != null &&
171
+ this.config.scoreThreshold != null &&
172
+ score < this.config.scoreThreshold
173
+ )
174
+ }
175
+
176
+ hashToken(value: string) {
177
+ const hash = createHash('sha256')
178
+ hash.update(this.config.tokenSalt)
179
+ hash.update(value)
180
+ return hash.digest().toString('base64')
181
+ }
182
+ }
@@ -8,7 +8,43 @@ export type AssetRef = {
8
8
  }
9
9
 
10
10
  export type Attrs = Record<string, boolean | string | undefined>
11
- export type LinkAttrs = { href: string } & Attrs
11
+
12
+ /**
13
+ * @see {@link https://developer.mozilla.org/fr/docs/Web/HTML/Attributes/rel}
14
+ */
15
+ const ALLOWED_LINK_REL_VALUES = Object.freeze([
16
+ 'alternate',
17
+ 'author',
18
+ 'canonical',
19
+ 'dns-prefetch',
20
+ 'external',
21
+ 'expect',
22
+ 'help',
23
+ 'icon',
24
+ 'license',
25
+ 'manifest',
26
+ 'me',
27
+ 'modulepreload',
28
+ 'next',
29
+ 'pingback',
30
+ 'preconnect',
31
+ 'prefetch',
32
+ 'preload',
33
+ 'prerender',
34
+ 'prev',
35
+ 'privacy-policy',
36
+ 'search',
37
+ 'stylesheet',
38
+ 'terms-of-service',
39
+ ] as const)
40
+ export type LinkRel = (typeof ALLOWED_LINK_REL_VALUES)[number]
41
+ export const isLinkRel = (rel: unknown): rel is LinkRel =>
42
+ (ALLOWED_LINK_REL_VALUES as readonly unknown[]).includes(rel)
43
+
44
+ export type LinkAttrs = Attrs & {
45
+ href: string
46
+ rel: LinkRel
47
+ }
12
48
  export type MetaAttrs =
13
49
  | { name: string; content: string }
14
50
  | { 'http-equiv': string; content: string }
@@ -27,7 +63,7 @@ export type BuildDocumentOptions = {
27
63
  title?: HtmlValue
28
64
  scripts?: readonly (Html | AssetRef)[]
29
65
  styles?: readonly (Html | AssetRef)[]
30
- body: HtmlValue
66
+ body?: HtmlValue
31
67
  bodyAttrs?: Attrs
32
68
  }
33
69
 
@@ -50,12 +86,13 @@ export const buildDocument = ({
50
86
  ${base && html`<base href="${base.href}" />`}
51
87
  ${meta?.some(isViewportMeta) ? null : defaultViewport}
52
88
  ${meta?.map(metaToHtml)}
89
+ ${styles?.map(linkPreload('style'))}
90
+ ${scripts?.map(linkPreload('script'))}
53
91
  ${links?.map(linkToHtml)}
54
- ${head} ${styles?.map(styleToHtml)}
92
+ ${head}
93
+ ${styles?.map(styleToHtml)}
55
94
  </head>
56
- <body${attrsToHtml(bodyAttrs)}>
57
- ${body} ${scripts?.map(scriptToHtml)}
58
- </body>
95
+ <body${attrsToHtml(bodyAttrs)}>${body}${scripts?.map(scriptToHtml)}</body>
59
96
  </html>`
60
97
 
61
98
  function isViewportMeta<T extends MetaAttrs>(
@@ -64,12 +101,12 @@ function isViewportMeta<T extends MetaAttrs>(
64
101
  return 'name' in attrs && attrs.name === 'viewport'
65
102
  }
66
103
 
67
- function* linkToHtml(attrs: LinkAttrs) {
68
- yield html`<link${attrsToHtml(attrs)} />`
104
+ function linkToHtml(attrs: LinkAttrs) {
105
+ return html`<link${attrsToHtml(attrs)} />`
69
106
  }
70
107
 
71
- function* metaToHtml(attrs: MetaAttrs) {
72
- yield html`<meta${attrsToHtml(attrs)} />`
108
+ function metaToHtml(attrs: MetaAttrs) {
109
+ return html`<meta${attrsToHtml(attrs)} />`
73
110
  }
74
111
 
75
112
  function* attrsToHtml(attrs?: Attrs) {
@@ -83,16 +120,23 @@ function* attrsToHtml(attrs?: Attrs) {
83
120
  }
84
121
  }
85
122
 
86
- function* scriptToHtml(script: Html | AssetRef) {
87
- yield script instanceof Html
123
+ function linkPreload(as: 'script' | 'style') {
124
+ return (style: Html | AssetRef) =>
125
+ style instanceof Html
126
+ ? undefined
127
+ : html`<link rel="preload" href="${style.url}" as="${as}" />`
128
+ }
129
+
130
+ function scriptToHtml(script: Html | AssetRef) {
131
+ return script instanceof Html
88
132
  ? // prettier-ignore
89
133
  html`<script>${script}</script>` // hash validity requires no space around the content
90
- : html`<script type="module" src="${script.url}?${script.sha256}"></script>`
134
+ : html`<script type="module" src="${script.url}"></script>`
91
135
  }
92
136
 
93
- function* styleToHtml(style: Html | AssetRef) {
94
- yield style instanceof Html
137
+ function styleToHtml(style: Html | AssetRef) {
138
+ return style instanceof Html
95
139
  ? // prettier-ignore
96
140
  html`<style>${style}</style>` // hash validity requires no space around the content
97
- : html`<link rel="stylesheet" href="${style.url}?${style.sha256}" />`
141
+ : html`<link rel="stylesheet" href="${style.url}" />`
98
142
  }
@@ -2,6 +2,8 @@ import type { IncomingMessage, ServerResponse } from 'node:http'
2
2
  import { writeJson } from './response.js'
3
3
  import { Handler, Middleware, NextFunction } from './types.js'
4
4
 
5
+ const isNonNullable = <X>(x: X): x is NonNullable<X> => x != null
6
+
5
7
  export function combineMiddlewares<M extends Middleware<any, any, any>>(
6
8
  middlewares: Iterable<null | undefined | M>,
7
9
  options?: { skipKeyword?: string },
@@ -15,12 +17,11 @@ export function combineMiddlewares(
15
17
  middlewares: Iterable<null | undefined | Middleware<unknown>>,
16
18
  { skipKeyword }: { skipKeyword?: string } = {},
17
19
  ): Middleware<unknown> {
18
- const middlewaresArray = Array.from(middlewares).filter(
19
- (x): x is NonNullable<typeof x> => x != null,
20
- )
20
+ const middlewaresArray = Array.from(middlewares).filter(isNonNullable)
21
21
 
22
22
  // Optimization: if there are no middlewares, return a noop middleware.
23
23
  if (middlewaresArray.length === 0) return (req, res, next) => void next()
24
+ if (middlewaresArray.length === 1) return middlewaresArray[0]
24
25
 
25
26
  return function (req, res, next) {
26
27
  let i = 0
@@ -1,5 +1,6 @@
1
1
  import { randomBytes } from 'node:crypto'
2
2
  import type { IncomingMessage, ServerResponse } from 'node:http'
3
+ import { languages, mediaType } from '@hapi/accept'
3
4
  import { parse as parseCookie, serialize as serializeCookie } from 'cookie'
4
5
  import forwarded from 'forwarded'
5
6
  import createHttpError from 'http-errors'
@@ -79,6 +80,18 @@ export function validateFetchSite(
79
80
  validateHeaderValue(req, 'sec-fetch-site', expectedSite)
80
81
  }
81
82
 
83
+ export function validateReferer(
84
+ req: IncomingMessage,
85
+ res: ServerResponse,
86
+ reference: UrlReference,
87
+ allowNull: true,
88
+ ): URL | null
89
+ export function validateReferer(
90
+ req: IncomingMessage,
91
+ res: ServerResponse,
92
+ reference: UrlReference,
93
+ allowNull?: false,
94
+ ): URL
82
95
  export function validateReferer(
83
96
  req: IncomingMessage,
84
97
  res: ServerResponse,
@@ -90,6 +103,7 @@ export function validateReferer(
90
103
  if (refererUrl ? !urlMatch(refererUrl, reference) : !allowNull) {
91
104
  throw createHttpError(400, `Invalid referer ${referer}`)
92
105
  }
106
+ return refererUrl
93
107
  }
94
108
 
95
109
  export async function setupCsrfToken(
@@ -126,12 +140,13 @@ export function validateSameOrigin(
126
140
  export function validateCsrfToken(
127
141
  req: IncomingMessage,
128
142
  res: ServerResponse,
129
- csrfToken: string,
143
+ csrfToken: unknown,
130
144
  cookieName = 'csrf_token',
131
145
  clearCookie = false,
132
146
  ) {
133
147
  const cookies = parseHttpCookies(req)
134
148
  if (
149
+ typeof csrfToken !== 'string' ||
135
150
  !csrfToken ||
136
151
  !cookies ||
137
152
  !cookieName ||
@@ -248,3 +263,18 @@ function extractPort(req: IncomingMessage, ip: string): number {
248
263
 
249
264
  throw new Error('Could not determine port')
250
265
  }
266
+
267
+ export function extractLocales(req: IncomingMessage) {
268
+ const acceptLanguage = req.headers['accept-language']
269
+ return acceptLanguage ? languages(acceptLanguage) : []
270
+ }
271
+
272
+ export function negotiateResponseContent<T extends string>(
273
+ req: IncomingMessage,
274
+ types: readonly T[],
275
+ ): T | undefined {
276
+ const type = mediaType(req.headers['accept'], types)
277
+ if (type) return type as T
278
+
279
+ return undefined
280
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ServerResponse } from 'node:http'
2
2
  import { type Readable, pipeline } from 'node:stream'
3
- import { Handler } from './types.js'
3
+ import type { Handler, Middleware } from './types.js'
4
4
 
5
5
  export function appendHeader(
6
6
  res: ServerResponse,
@@ -53,22 +53,27 @@ export function writeStream(
53
53
  export function writeBuffer(
54
54
  res: ServerResponse,
55
55
  chunk: string | Buffer,
56
- {
57
- status = 200,
58
- contentType = 'application/octet-stream',
59
- }: WriteResponseOptions = {},
56
+ opts: WriteResponseOptions,
60
57
  ): void {
61
- res.statusCode = status
62
- res.setHeader('content-type', contentType)
58
+ if (opts?.status != null) res.statusCode = opts.status
59
+ res.setHeader('content-type', opts?.contentType || 'application/octet-stream')
63
60
  res.end(chunk)
64
61
  }
65
62
 
63
+ export function toJsonBuffer(value: unknown): Buffer {
64
+ try {
65
+ return Buffer.from(JSON.stringify(value))
66
+ } catch (cause) {
67
+ throw new Error(`Failed to serialize as JSON`, { cause })
68
+ }
69
+ }
70
+
66
71
  export function writeJson(
67
72
  res: ServerResponse,
68
73
  payload: unknown,
69
74
  { contentType = 'application/json', ...options }: WriteResponseOptions = {},
70
75
  ): void {
71
- const buffer = Buffer.from(JSON.stringify(payload))
76
+ const buffer = toJsonBuffer(payload)
72
77
  writeBuffer(res, buffer, { ...options, contentType })
73
78
  }
74
79
 
@@ -76,7 +81,7 @@ export function staticJsonMiddleware(
76
81
  value: unknown,
77
82
  { contentType = 'application/json', ...options }: WriteResponseOptions = {},
78
83
  ): Handler<unknown> {
79
- const buffer = Buffer.from(JSON.stringify(value))
84
+ const buffer = toJsonBuffer(value)
80
85
  const staticOptions: WriteResponseOptions = { ...options, contentType }
81
86
  return function (req, res) {
82
87
  writeBuffer(res, buffer, staticOptions)
@@ -90,3 +95,11 @@ export function writeHtml(
90
95
  ): void {
91
96
  writeBuffer(res, html, { ...options, contentType })
92
97
  }
98
+
99
+ export function cacheControlMiddleware(maxAge: number): Middleware<void> {
100
+ const header = `max-age=${maxAge}`
101
+ return function (req, res, next) {
102
+ res.setHeader('Cache-Control', header)
103
+ next()
104
+ }
105
+ }
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod'
2
+
3
+ export const localeSchema = z
4
+ .string()
5
+ .regex(/^[a-z]{2,3}(-[A-Z]{2})?$/, 'Invalid locale')
6
+ export type Locale = z.infer<typeof localeSchema>
7
+
8
+ export const multiLangStringSchema = z.intersection(
9
+ z.object({ en: z.string() }), // en is required
10
+ z.record(localeSchema, z.union([z.string(), z.undefined()])),
11
+ )
12
+ export type MultiLangString = z.infer<typeof multiLangStringSchema>
13
+
14
+ export const AVAILABLE_LOCALES = [
15
+ // TODO: Add more in this list as translations are added in the PO files
16
+ 'en',
17
+ 'fr',
18
+ ] as const satisfies readonly Locale[]
19
+ export type AvailableLocale = (typeof AVAILABLE_LOCALES)[number]
20
+ export const isAvailableLocale = (v: unknown): v is AvailableLocale =>
21
+ (AVAILABLE_LOCALES as readonly unknown[]).includes(v)
@@ -6,17 +6,14 @@
6
6
  * particularly useful when the function is a member of a "private" object.
7
7
  */
8
8
  export async function callAsync<F extends (...args: any[]) => unknown>(
9
- this: ThisParameterType<F>,
10
9
  fn: F,
11
10
  ...args: Parameters<F>
12
11
  ): Promise<Awaited<ReturnType<F>>>
13
12
  export async function callAsync<F extends (...args: any[]) => unknown>(
14
- this: ThisParameterType<F>,
15
13
  fn?: F,
16
14
  ...args: Parameters<F>
17
15
  ): Promise<Awaited<ReturnType<F>> | undefined>
18
16
  export async function callAsync<F extends (...args: any[]) => unknown>(
19
- this: ThisParameterType<F>,
20
17
  fn?: F,
21
18
  ...args: Parameters<F>
22
19
  ): Promise<Awaited<ReturnType<F>> | undefined> {
@@ -1,4 +1,133 @@
1
1
  // eslint-disable-next-line @typescript-eslint/ban-types
2
2
  export type Simplify<T> = { [K in keyof T]: T[K] } & {}
3
- export type Override<T, V> = Simplify<V & Omit<T, keyof V>>
3
+ export type Override<T, V> = Simplify<{
4
+ [K in keyof (V & T)]: K extends keyof V
5
+ ? V[K]
6
+ : K extends keyof T
7
+ ? T[K]
8
+ : never
9
+ }>
4
10
  export type Awaitable<T> = T | Promise<T>
11
+
12
+ /**
13
+ * Similar to {@link Required} but also ensures that all values are defined.
14
+ */
15
+ export type RequiredDefined<T> = { [K in keyof T]-?: Exclude<T[K], undefined> }
16
+
17
+ // <hardcore-mode> (don't touch this)
18
+
19
+ /**
20
+ * @example
21
+ * ```ts
22
+ * type F = UnionToFnUnion<'a' | 'b'> // (() => 'a') | (() => 'b')
23
+ * ```
24
+ */
25
+ type UnionToFnUnion<T> = T extends any ? () => T : never
26
+
27
+ /**
28
+ * @example
29
+ * ```ts
30
+ * type A = UnionToIntersection<(() => 'a') | (() => 'b')> // (() => 'a') & (() => 'b')
31
+ *
32
+ * UnionToIntersection<{ foo: string | number } | { foo: number; bar: 4 }> // { foo: number; bar: 4 }
33
+ * ```
34
+ */
35
+ type UnionToIntersection<T> = (T extends any ? (x: T) => void : never) extends (
36
+ x: infer U,
37
+ ) => void
38
+ ? U
39
+ : never
40
+
41
+ /**
42
+ * @example
43
+ * ```ts
44
+ * type B = ExtractUnionItem<'a' | 'b'> // 'b'
45
+ * ```
46
+ */
47
+ type ExtractUnionItem<T> =
48
+ // There exists a quirk in the way TypeScript works when inferring return
49
+ // types of an (disjoined) intersection of functions:
50
+ //
51
+ // type AnB = (() => 'a') & (() => 'b')
52
+ // type B = AnB extends () => infer R ? R : never // 'b'
53
+ //
54
+ // By turning the input union T (e.g. 'a' | 'b') into a union of function
55
+ // (() => 'a') | (() => 'b') and then into an intersection of those functions
56
+ // (() => 'a') & (() => 'b'), we can exploit the special TypeScript behavior
57
+ // to infer only the last return type from the functions, which is effectively
58
+ // equal to the last item of the input union T.
59
+ UnionToIntersection<UnionToFnUnion<T>> extends () => infer R ? R : never
60
+
61
+ /**
62
+ * Utility that turn a union of types (`'a' | 'b'`) into a tuple with matching
63
+ * types (`['a', 'b']`).
64
+ *
65
+ * @note this only work with unions of "const" types. Using this with globals
66
+ * types (`string`, etc.) will yield unexpected results.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * type T = UnionToTuple<'a' | 'b'> // ['a', 'b']
71
+ * type T = UnionToTuple<'a' | 'b' | 'c'> // ['a', 'b', 'c']
72
+ * ```
73
+ */
74
+ type UnionToTuple<T> = UnionToTupleInternal<T>
75
+
76
+ type UnionToTupleInternal<
77
+ T,
78
+ // Accumulator for terminal recursivity (initialized to empty tuple)
79
+ Acc extends readonly any[] = [],
80
+ // Get the next item from the union (if any)
81
+ Next = ExtractUnionItem<T>,
82
+ > =
83
+ // If there were no more items to extract from the union T, then we are done
84
+ [Next] extends [never]
85
+ ? // Return result of previous recursive calls
86
+ Acc
87
+ : // Recursively call UnionToTupleInternal by Exclude'ing the Next item from
88
+ // the union (T) and adding it to the accumulator.
89
+ UnionToTupleInternal<Exclude<T, Next>, readonly [Next, ...Acc]>
90
+
91
+ /**
92
+ * This utility allows to create an assertion function that checks if a
93
+ * particular interface is fully implemented by some value.
94
+ *
95
+ * The use of the (rather complex) {@link UnionToTuple} allows to ensure that,
96
+ * at runtime, all the required interface keys are indeed checked, and that
97
+ * future additions to the interface do not result in a false sense of type
98
+ * safety.
99
+ *
100
+ * @note This function should not be made public, as it relies on a quirk of
101
+ * TypeScript to work properly.
102
+ *
103
+ * @example Valid use
104
+ *
105
+ * ```ts
106
+ * const isFoo = buildInterfaceChecker<{ foo: string }>(['foo'])
107
+ * const isFooBar = buildInterfaceChecker<{ foo: string; bar: boolean }>([
108
+ * 'foo',
109
+ * 'bar',
110
+ * ])
111
+ *
112
+ * declare const val: { foo?: string }
113
+ *
114
+ * if (isFoo(val)) {
115
+ * val // { foo: string }
116
+ * }
117
+ * ```
118
+ *
119
+ * @example Use cases where the runtime keys do not match the interface keys
120
+ *
121
+ * ```ts
122
+ * buildInterfaceChecker<{ foo: string }>([])
123
+ * buildInterfaceChecker<{ foo: string }>(['fee'])
124
+ * buildInterfaceChecker<{ foo: string; bar: string }>(['foo'])
125
+ * buildInterfaceChecker<{ foo: string; bar: string }>(['foo', 'baz'])
126
+ * ```
127
+ */
128
+ export const buildInterfaceChecker =
129
+ <I extends object>(keys: readonly string[] & UnionToTuple<keyof I>) =>
130
+ <V extends Partial<I>>(value: V): value is V & RequiredDefined<I> =>
131
+ keys.every((name) => value[name] !== undefined)
132
+
133
+ // </hardcore-mode>
@@ -1,6 +1,7 @@
1
1
  import { Keyset } from '@atproto/jwk'
2
2
  import {
3
3
  OAuthAuthorizationServerMetadata,
4
+ OAuthIssuerIdentifier,
4
5
  oauthAuthorizationServerMetadataSchema,
5
6
  } from '@atproto/oauth-types'
6
7
  import { Client } from '../client/client.js'
@@ -17,7 +18,7 @@ export type CustomMetadata = {
17
18
  * @see {@link https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata}
18
19
  */
19
20
  export function buildMetadata(
20
- issuer: string,
21
+ issuer: OAuthIssuerIdentifier,
21
22
  keyset: Keyset,
22
23
  customMetadata?: CustomMetadata,
23
24
  ): OAuthAuthorizationServerMetadata {
@@ -4,6 +4,7 @@ export { OAuthError } from './errors/oauth-error.js'
4
4
  export { AccessDeniedError } from './errors/access-denied-error.js'
5
5
  export { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
6
6
  export { ConsentRequiredError } from './errors/consent-required-error.js'
7
+ export { HandleUnavailableError } from './errors/handle-unavailable-error.js'
7
8
  export { InvalidAuthorizationDetailsError } from './errors/invalid-authorization-details-error.js'
8
9
  export { InvalidClientError } from './errors/invalid-client-error.js'
9
10
  export { InvalidClientIdError } from './errors/invalid-client-id-error.js'