@atproto/oauth-provider 0.5.1 → 0.6.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 (325) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/account/account-manager.d.ts +7 -5
  3. package/dist/account/account-manager.d.ts.map +1 -1
  4. package/dist/account/account-manager.js +34 -25
  5. package/dist/account/account-manager.js.map +1 -1
  6. package/dist/account/account-store.d.ts +13 -5
  7. package/dist/account/account-store.d.ts.map +1 -1
  8. package/dist/account/account-store.js +24 -8
  9. package/dist/account/account-store.js.map +1 -1
  10. package/dist/account/account.d.ts +1 -11
  11. package/dist/account/account.d.ts.map +1 -1
  12. package/dist/account/{sign-up-data.d.ts → sign-up-input.d.ts} +5 -5
  13. package/dist/account/sign-up-input.d.ts.map +1 -0
  14. package/dist/account/{sign-up-data.js → sign-up-input.js} +3 -3
  15. package/dist/account/sign-up-input.js.map +1 -0
  16. package/dist/assets/assets-middleware.d.ts +2 -0
  17. package/dist/assets/assets-middleware.d.ts.map +1 -1
  18. package/dist/assets/assets-middleware.js +12 -14
  19. package/dist/assets/assets-middleware.js.map +1 -1
  20. package/dist/errors/invalid-invite-code-error.d.ts +5 -0
  21. package/dist/errors/invalid-invite-code-error.d.ts.map +1 -0
  22. package/dist/errors/invalid-invite-code-error.js +11 -0
  23. package/dist/errors/invalid-invite-code-error.js.map +1 -0
  24. package/dist/errors/oauth-error.d.ts +2 -2
  25. package/dist/errors/oauth-error.js.map +1 -1
  26. package/dist/lib/csp/index.d.ts +5 -6
  27. package/dist/lib/csp/index.d.ts.map +1 -1
  28. package/dist/lib/csp/index.js +14 -11
  29. package/dist/lib/csp/index.js.map +1 -1
  30. package/dist/lib/hcaptcha.d.ts +5 -3
  31. package/dist/lib/hcaptcha.d.ts.map +1 -1
  32. package/dist/lib/hcaptcha.js +7 -4
  33. package/dist/lib/hcaptcha.js.map +1 -1
  34. package/dist/lib/html/build-document.d.ts +2 -2
  35. package/dist/lib/html/build-document.d.ts.map +1 -1
  36. package/dist/lib/html/build-document.js +11 -7
  37. package/dist/lib/html/build-document.js.map +1 -1
  38. package/dist/lib/html/html.d.ts.map +1 -1
  39. package/dist/lib/html/html.js +10 -13
  40. package/dist/lib/html/html.js.map +1 -1
  41. package/dist/lib/html/util.d.ts +0 -1
  42. package/dist/lib/html/util.d.ts.map +1 -1
  43. package/dist/lib/html/util.js +0 -4
  44. package/dist/lib/html/util.js.map +1 -1
  45. package/dist/lib/http/response.d.ts +3 -1
  46. package/dist/lib/http/response.d.ts.map +1 -1
  47. package/dist/lib/http/response.js +3 -0
  48. package/dist/lib/http/response.js.map +1 -1
  49. package/dist/lib/http/security-headers.d.ts +48 -0
  50. package/dist/lib/http/security-headers.d.ts.map +1 -0
  51. package/dist/lib/http/security-headers.js +62 -0
  52. package/dist/lib/http/security-headers.js.map +1 -0
  53. package/dist/lib/util/type.d.ts +8 -0
  54. package/dist/lib/util/type.d.ts.map +1 -1
  55. package/dist/lib/util/type.js.map +1 -1
  56. package/dist/oauth-errors.d.ts +1 -0
  57. package/dist/oauth-errors.d.ts.map +1 -1
  58. package/dist/oauth-errors.js +3 -1
  59. package/dist/oauth-errors.js.map +1 -1
  60. package/dist/oauth-hooks.d.ts +4 -25
  61. package/dist/oauth-hooks.d.ts.map +1 -1
  62. package/dist/oauth-provider.d.ts.map +1 -1
  63. package/dist/oauth-provider.js +26 -25
  64. package/dist/oauth-provider.js.map +1 -1
  65. package/dist/output/backend-data.d.ts +4 -0
  66. package/dist/output/backend-data.d.ts.map +1 -0
  67. package/dist/output/backend-data.js +19 -0
  68. package/dist/output/backend-data.js.map +1 -0
  69. package/dist/output/build-authorize-data.d.ts +3 -19
  70. package/dist/output/build-authorize-data.d.ts.map +1 -1
  71. package/dist/output/build-authorize-data.js.map +1 -1
  72. package/dist/output/build-customization-data.d.ts +11 -18
  73. package/dist/output/build-customization-data.d.ts.map +1 -1
  74. package/dist/output/build-customization-data.js +1 -1
  75. package/dist/output/build-customization-data.js.map +1 -1
  76. package/dist/output/build-error-data.d.ts +3 -0
  77. package/dist/output/build-error-data.d.ts.map +1 -0
  78. package/dist/output/build-error-data.js +10 -0
  79. package/dist/output/build-error-data.js.map +1 -0
  80. package/dist/output/build-error-payload.d.ts +2 -1
  81. package/dist/output/build-error-payload.d.ts.map +1 -1
  82. package/dist/output/build-error-payload.js.map +1 -1
  83. package/dist/output/output-manager.d.ts +10 -4
  84. package/dist/output/output-manager.d.ts.map +1 -1
  85. package/dist/output/output-manager.js +68 -39
  86. package/dist/output/output-manager.js.map +1 -1
  87. package/dist/output/send-web-page.d.ts +6 -10
  88. package/dist/output/send-web-page.d.ts.map +1 -1
  89. package/dist/output/send-web-page.js +27 -47
  90. package/dist/output/send-web-page.js.map +1 -1
  91. package/dist/signer/signed-token-payload.d.ts +3 -3
  92. package/dist/signer/signer.d.ts +2 -2
  93. package/package.json +7 -39
  94. package/src/account/account-manager.ts +55 -34
  95. package/src/account/account-store.ts +29 -6
  96. package/src/account/account.ts +1 -14
  97. package/src/account/{sign-up-data.ts → sign-up-input.ts} +2 -2
  98. package/src/assets/assets-middleware.ts +11 -17
  99. package/src/errors/invalid-invite-code-error.ts +10 -0
  100. package/src/errors/oauth-error.ts +1 -1
  101. package/src/lib/csp/index.ts +16 -13
  102. package/src/lib/hcaptcha.ts +10 -7
  103. package/src/lib/html/build-document.ts +15 -8
  104. package/src/lib/html/html.ts +11 -18
  105. package/src/lib/html/util.ts +0 -4
  106. package/src/lib/http/response.ts +9 -1
  107. package/src/lib/http/security-headers.ts +91 -0
  108. package/src/lib/util/type.ts +18 -0
  109. package/src/oauth-errors.ts +1 -0
  110. package/src/oauth-hooks.ts +4 -25
  111. package/src/oauth-provider.ts +40 -34
  112. package/src/output/backend-data.ts +18 -0
  113. package/src/output/build-authorize-data.ts +3 -26
  114. package/src/output/build-customization-data.ts +2 -13
  115. package/src/output/build-error-data.ts +8 -0
  116. package/src/output/build-error-payload.ts +4 -2
  117. package/src/output/output-manager.ts +86 -47
  118. package/src/output/send-web-page.ts +29 -58
  119. package/tsconfig.backend.json +1 -2
  120. package/tsconfig.backend.tsbuildinfo +1 -1
  121. package/tsconfig.json +1 -5
  122. package/.linguirc +0 -57
  123. package/dist/account/sign-up-data.d.ts.map +0 -1
  124. package/dist/account/sign-up-data.js.map +0 -1
  125. package/dist/assets/app/bundle-manifest.json +0 -614
  126. package/dist/assets/app/index-ItwwtJ8r.js +0 -36
  127. package/dist/assets/app/index-ItwwtJ8r.js.map +0 -1
  128. package/dist/assets/app/main-B_dNxQo_.js +0 -4
  129. package/dist/assets/app/main-B_dNxQo_.js.map +0 -1
  130. package/dist/assets/app/main-CSatvmRR.css +0 -3
  131. package/dist/assets/app/main-CSatvmRR.js +0 -306
  132. package/dist/assets/app/main-CSatvmRR.js.map +0 -1
  133. package/dist/assets/app/messages-BQeltXSF.js +0 -4
  134. package/dist/assets/app/messages-BQeltXSF.js.map +0 -1
  135. package/dist/assets/app/messages-BQkEhfjg.js +0 -4
  136. package/dist/assets/app/messages-BQkEhfjg.js.map +0 -1
  137. package/dist/assets/app/messages-BUjKj_UJ.js +0 -4
  138. package/dist/assets/app/messages-BUjKj_UJ.js.map +0 -1
  139. package/dist/assets/app/messages-BWIQa8fO.js +0 -4
  140. package/dist/assets/app/messages-BWIQa8fO.js.map +0 -1
  141. package/dist/assets/app/messages-BaNVb0bp.js +0 -4
  142. package/dist/assets/app/messages-BaNVb0bp.js.map +0 -1
  143. package/dist/assets/app/messages-BaizVXcF.js +0 -4
  144. package/dist/assets/app/messages-BaizVXcF.js.map +0 -1
  145. package/dist/assets/app/messages-BfoClA1Y.js +0 -4
  146. package/dist/assets/app/messages-BfoClA1Y.js.map +0 -1
  147. package/dist/assets/app/messages-BsKGDZnC.js +0 -4
  148. package/dist/assets/app/messages-BsKGDZnC.js.map +0 -1
  149. package/dist/assets/app/messages-Bu-TJhml.js +0 -4
  150. package/dist/assets/app/messages-Bu-TJhml.js.map +0 -1
  151. package/dist/assets/app/messages-BvOKnBQk.js +0 -4
  152. package/dist/assets/app/messages-BvOKnBQk.js.map +0 -1
  153. package/dist/assets/app/messages-BxDzCiWz.js +0 -4
  154. package/dist/assets/app/messages-BxDzCiWz.js.map +0 -1
  155. package/dist/assets/app/messages-CDgFOy4S.js +0 -4
  156. package/dist/assets/app/messages-CDgFOy4S.js.map +0 -1
  157. package/dist/assets/app/messages-CLbTz0o9.js +0 -4
  158. package/dist/assets/app/messages-CLbTz0o9.js.map +0 -1
  159. package/dist/assets/app/messages-CNwSh0t7.js +0 -4
  160. package/dist/assets/app/messages-CNwSh0t7.js.map +0 -1
  161. package/dist/assets/app/messages-CSMNJ6P8.js +0 -4
  162. package/dist/assets/app/messages-CSMNJ6P8.js.map +0 -1
  163. package/dist/assets/app/messages-CZQUw3mp.js +0 -4
  164. package/dist/assets/app/messages-CZQUw3mp.js.map +0 -1
  165. package/dist/assets/app/messages-CZT41oVp.js +0 -4
  166. package/dist/assets/app/messages-CZT41oVp.js.map +0 -1
  167. package/dist/assets/app/messages-C_b-d3t8.js +0 -4
  168. package/dist/assets/app/messages-C_b-d3t8.js.map +0 -1
  169. package/dist/assets/app/messages-C_u3MTc2.js +0 -4
  170. package/dist/assets/app/messages-C_u3MTc2.js.map +0 -1
  171. package/dist/assets/app/messages-Cn8nHZic.js +0 -4
  172. package/dist/assets/app/messages-Cn8nHZic.js.map +0 -1
  173. package/dist/assets/app/messages-CtDywJUm.js +0 -4
  174. package/dist/assets/app/messages-CtDywJUm.js.map +0 -1
  175. package/dist/assets/app/messages-CurtIjBF.js +0 -4
  176. package/dist/assets/app/messages-CurtIjBF.js.map +0 -1
  177. package/dist/assets/app/messages-Cv6zIbaP.js +0 -4
  178. package/dist/assets/app/messages-Cv6zIbaP.js.map +0 -1
  179. package/dist/assets/app/messages-D1eLQuPE.js +0 -4
  180. package/dist/assets/app/messages-D1eLQuPE.js.map +0 -1
  181. package/dist/assets/app/messages-D8vHEaYW.js +0 -4
  182. package/dist/assets/app/messages-D8vHEaYW.js.map +0 -1
  183. package/dist/assets/app/messages-DJ1Q4GeC.js +0 -4
  184. package/dist/assets/app/messages-DJ1Q4GeC.js.map +0 -1
  185. package/dist/assets/app/messages-DRL3exqd.js +0 -4
  186. package/dist/assets/app/messages-DRL3exqd.js.map +0 -1
  187. package/dist/assets/app/messages-DWLPQRTp.js +0 -4
  188. package/dist/assets/app/messages-DWLPQRTp.js.map +0 -1
  189. package/dist/assets/app/messages-DjVaE9YE.js +0 -4
  190. package/dist/assets/app/messages-DjVaE9YE.js.map +0 -1
  191. package/dist/assets/app/messages-DqpMfFJR.js +0 -4
  192. package/dist/assets/app/messages-DqpMfFJR.js.map +0 -1
  193. package/dist/assets/app/messages-ETjhJBEN.js +0 -4
  194. package/dist/assets/app/messages-ETjhJBEN.js.map +0 -1
  195. package/dist/assets/app/messages-EUKrgrGn.js +0 -4
  196. package/dist/assets/app/messages-EUKrgrGn.js.map +0 -1
  197. package/dist/assets/app/messages-QQrOUcPW.js +0 -4
  198. package/dist/assets/app/messages-QQrOUcPW.js.map +0 -1
  199. package/dist/assets/app/messages-e2QGqFL6.js +0 -4
  200. package/dist/assets/app/messages-e2QGqFL6.js.map +0 -1
  201. package/dist/assets/app/messages-p61py7gD.js +0 -4
  202. package/dist/assets/app/messages-p61py7gD.js.map +0 -1
  203. package/dist/assets/asset.d.ts +0 -9
  204. package/dist/assets/asset.d.ts.map +0 -1
  205. package/dist/assets/asset.js +0 -3
  206. package/dist/assets/asset.js.map +0 -1
  207. package/dist/assets/index.d.ts +0 -5
  208. package/dist/assets/index.d.ts.map +0 -1
  209. package/dist/assets/index.js +0 -78
  210. package/dist/assets/index.js.map +0 -1
  211. package/rollup.config.js +0 -98
  212. package/src/assets/app/app.tsx +0 -43
  213. package/src/assets/app/backend-data.ts +0 -27
  214. package/src/assets/app/backend-types.ts +0 -66
  215. package/src/assets/app/components/forms/button-toggle-visibility.tsx +0 -43
  216. package/src/assets/app/components/forms/button.tsx +0 -60
  217. package/src/assets/app/components/forms/fieldset.tsx +0 -55
  218. package/src/assets/app/components/forms/form-card-async.tsx +0 -103
  219. package/src/assets/app/components/forms/form-card.tsx +0 -49
  220. package/src/assets/app/components/forms/input-checkbox.tsx +0 -73
  221. package/src/assets/app/components/forms/input-container.tsx +0 -107
  222. package/src/assets/app/components/forms/input-email-address.tsx +0 -66
  223. package/src/assets/app/components/forms/input-new-password.tsx +0 -62
  224. package/src/assets/app/components/forms/input-password.tsx +0 -88
  225. package/src/assets/app/components/forms/input-text.tsx +0 -76
  226. package/src/assets/app/components/forms/input-token.tsx +0 -94
  227. package/src/assets/app/components/forms/wizard-card.tsx +0 -116
  228. package/src/assets/app/components/layouts/layout-title-page.tsx +0 -77
  229. package/src/assets/app/components/layouts/layout-welcome.tsx +0 -73
  230. package/src/assets/app/components/utils/account-identifier.tsx +0 -23
  231. package/src/assets/app/components/utils/account-image.tsx +0 -33
  232. package/src/assets/app/components/utils/admonition.tsx +0 -52
  233. package/src/assets/app/components/utils/client-name.tsx +0 -45
  234. package/src/assets/app/components/utils/error-card.tsx +0 -93
  235. package/src/assets/app/components/utils/error-message.tsx +0 -62
  236. package/src/assets/app/components/utils/help-card.tsx +0 -46
  237. package/src/assets/app/components/utils/icons.tsx +0 -88
  238. package/src/assets/app/components/utils/link-anchor.tsx +0 -28
  239. package/src/assets/app/components/utils/link-title.tsx +0 -26
  240. package/src/assets/app/components/utils/multi-lang-string.tsx +0 -56
  241. package/src/assets/app/components/utils/password-strength-label.tsx +0 -37
  242. package/src/assets/app/components/utils/password-strength-meter.tsx +0 -58
  243. package/src/assets/app/components/utils/url-viewer.tsx +0 -73
  244. package/src/assets/app/cookies.ts +0 -11
  245. package/src/assets/app/hooks/use-api.ts +0 -178
  246. package/src/assets/app/hooks/use-async-action.ts +0 -120
  247. package/src/assets/app/hooks/use-bound-dispatch.ts +0 -5
  248. package/src/assets/app/hooks/use-browser-color-scheme.ts +0 -31
  249. package/src/assets/app/hooks/use-csrf-token.ts +0 -5
  250. package/src/assets/app/hooks/use-random-string.ts +0 -37
  251. package/src/assets/app/hooks/use-stepper.ts +0 -87
  252. package/src/assets/app/index.html +0 -182
  253. package/src/assets/app/lib/api.ts +0 -267
  254. package/src/assets/app/lib/clsx.ts +0 -6
  255. package/src/assets/app/lib/json-client.ts +0 -94
  256. package/src/assets/app/lib/password.ts +0 -98
  257. package/src/assets/app/lib/ref.ts +0 -17
  258. package/src/assets/app/lib/util.ts +0 -13
  259. package/src/assets/app/locales/an/messages.po +0 -492
  260. package/src/assets/app/locales/ast/messages.po +0 -492
  261. package/src/assets/app/locales/ca/messages.po +0 -492
  262. package/src/assets/app/locales/da/messages.po +0 -492
  263. package/src/assets/app/locales/de/messages.po +0 -492
  264. package/src/assets/app/locales/el/messages.po +0 -492
  265. package/src/assets/app/locales/en/messages.po +0 -492
  266. package/src/assets/app/locales/en-GB/messages.po +0 -492
  267. package/src/assets/app/locales/es/messages.po +0 -492
  268. package/src/assets/app/locales/eu/messages.po +0 -492
  269. package/src/assets/app/locales/fi/messages.po +0 -492
  270. package/src/assets/app/locales/fr/messages.po +0 -492
  271. package/src/assets/app/locales/ga/messages.po +0 -492
  272. package/src/assets/app/locales/gl/messages.po +0 -492
  273. package/src/assets/app/locales/hi/messages.po +0 -492
  274. package/src/assets/app/locales/hu/messages.po +0 -492
  275. package/src/assets/app/locales/ia/messages.po +0 -492
  276. package/src/assets/app/locales/id/messages.po +0 -492
  277. package/src/assets/app/locales/it/messages.po +0 -492
  278. package/src/assets/app/locales/ja/messages.po +0 -492
  279. package/src/assets/app/locales/km/messages.po +0 -492
  280. package/src/assets/app/locales/ko/messages.po +0 -492
  281. package/src/assets/app/locales/load.ts +0 -8
  282. package/src/assets/app/locales/locale-context.ts +0 -19
  283. package/src/assets/app/locales/locale-provider.tsx +0 -112
  284. package/src/assets/app/locales/locale-selector.tsx +0 -58
  285. package/src/assets/app/locales/locales.ts +0 -168
  286. package/src/assets/app/locales/ne/messages.po +0 -492
  287. package/src/assets/app/locales/nl/messages.po +0 -492
  288. package/src/assets/app/locales/pl/messages.po +0 -492
  289. package/src/assets/app/locales/pt-BR/messages.po +0 -492
  290. package/src/assets/app/locales/ro/messages.po +0 -492
  291. package/src/assets/app/locales/ru/messages.po +0 -492
  292. package/src/assets/app/locales/sv/messages.po +0 -492
  293. package/src/assets/app/locales/th/messages.po +0 -492
  294. package/src/assets/app/locales/tr/messages.po +0 -492
  295. package/src/assets/app/locales/uk/messages.po +0 -492
  296. package/src/assets/app/locales/vi/messages.po +0 -492
  297. package/src/assets/app/locales/zh-CN/messages.po +0 -492
  298. package/src/assets/app/locales/zh-HK/messages.po +0 -492
  299. package/src/assets/app/locales/zh-TW/messages.po +0 -492
  300. package/src/assets/app/main.css +0 -33
  301. package/src/assets/app/main.tsx +0 -44
  302. package/src/assets/app/views/authorize/accept/accept-form.tsx +0 -150
  303. package/src/assets/app/views/authorize/accept/accept-view.tsx +0 -70
  304. package/src/assets/app/views/authorize/authorize-view.tsx +0 -180
  305. package/src/assets/app/views/authorize/reset-password/reset-password-confirm-form.tsx +0 -88
  306. package/src/assets/app/views/authorize/reset-password/reset-password-request-form.tsx +0 -80
  307. package/src/assets/app/views/authorize/reset-password/reset-password-view.tsx +0 -127
  308. package/src/assets/app/views/authorize/sign-in/sign-in-form.tsx +0 -244
  309. package/src/assets/app/views/authorize/sign-in/sign-in-picker.tsx +0 -116
  310. package/src/assets/app/views/authorize/sign-in/sign-in-view.tsx +0 -145
  311. package/src/assets/app/views/authorize/sign-up/sign-up-account-form.tsx +0 -140
  312. package/src/assets/app/views/authorize/sign-up/sign-up-disclaimer.tsx +0 -51
  313. package/src/assets/app/views/authorize/sign-up/sign-up-handle-form.tsx +0 -289
  314. package/src/assets/app/views/authorize/sign-up/sign-up-hcaptcha-form.tsx +0 -108
  315. package/src/assets/app/views/authorize/sign-up/sign-up-view.tsx +0 -158
  316. package/src/assets/app/views/authorize/welcome/welcome-view.tsx +0 -56
  317. package/src/assets/app/views/error/error-view.tsx +0 -31
  318. package/src/assets/asset.ts +0 -9
  319. package/src/assets/index.ts +0 -86
  320. package/tailwind.config.js +0 -31
  321. package/tsconfig.frontend.json +0 -11
  322. package/tsconfig.frontend.tsbuildinfo +0 -1
  323. package/tsconfig.tools.json +0 -8
  324. package/tsconfig.tools.tsbuildinfo +0 -1
  325. package/vite.config.mjs +0 -16
@@ -9,6 +9,24 @@ export type Override<T, V> = Simplify<{
9
9
  }>
10
10
  export type Awaitable<T> = T | Promise<T>
11
11
 
12
+ /**
13
+ * Converts a tuple to the equivalent type of combining every item into a single
14
+ * one. If any of the item in the tuple is non nullish, the result will be non
15
+ * nullish.
16
+ */
17
+ export type CombinedTuple<T extends readonly unknown[]> = T extends []
18
+ ? undefined
19
+ : Exclude<
20
+ T[number],
21
+ // If any item in the tuple is never `null` (resp. `undefined`), exclude
22
+ // `null` (resp. `undefined`) from `T[number]`
23
+ {
24
+ [K in keyof T]-?:
25
+ | (null extends T[K] ? never : null)
26
+ | (undefined extends T[K] ? never : undefined)
27
+ }[keyof T]
28
+ >
29
+
12
30
  /**
13
31
  * Similar to {@link Required} but also ensures that all values are defined.
14
32
  */
@@ -12,6 +12,7 @@ export { InvalidClientMetadataError } from './errors/invalid-client-metadata-err
12
12
  export { InvalidDpopKeyBindingError } from './errors/invalid-dpop-key-binding-error.js'
13
13
  export { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js'
14
14
  export { InvalidGrantError } from './errors/invalid-grant-error.js'
15
+ export { InvalidInviteCodeError } from './errors/invalid-invite-code-error.js'
15
16
  export { InvalidParametersError } from './errors/invalid-parameters-error.js'
16
17
  export { InvalidRedirectUriError } from './errors/invalid-redirect-uri-error.js'
17
18
  export { InvalidRequestError } from './errors/invalid-request-error.js'
@@ -7,7 +7,7 @@ import {
7
7
  } from '@atproto/oauth-types'
8
8
  import { Account } from './account/account.js'
9
9
  import { SignInData } from './account/sign-in-data.js'
10
- import { SignUpData } from './account/sign-up-data.js'
10
+ import { SignUpInput } from './account/sign-up-input.js'
11
11
  import { ClientAuth } from './client/client-auth.js'
12
12
  import { ClientId } from './client/client-id.js'
13
13
  import { ClientInfo } from './client/client-info.js'
@@ -17,7 +17,7 @@ import { HcaptchaConfig, HcaptchaVerifyResult } from './lib/hcaptcha.js'
17
17
  import { RequestMetadata } from './lib/http/request.js'
18
18
  import { Awaitable } from './lib/util/type.js'
19
19
  import { AccessDeniedError, OAuthError } from './oauth-errors.js'
20
- import { DeviceAccountInfo, DeviceId } from './oauth-store.js'
20
+ import { DeviceAccountInfo, DeviceId, SignUpData } from './oauth-store.js'
21
21
 
22
22
  // Make sure all types needed to implement the OAuthHooks are exported
23
23
  export {
@@ -42,6 +42,7 @@ export {
42
42
  type RequestMetadata,
43
43
  type SignInData,
44
44
  type SignUpData,
45
+ type SignUpInput,
45
46
  }
46
47
 
47
48
  export type OAuthHooks = {
@@ -71,36 +72,14 @@ export type OAuthHooks = {
71
72
  account: Account
72
73
  }) => Awaitable<undefined | OAuthAuthorizationDetails>
73
74
 
74
- /**
75
- * This hook is called whenever an hcaptcha challenge is verified
76
- * during sign-up (if hcaptcha is enabled).
77
- *
78
- * @throws {InvalidRequestError} to deny the sign-up
79
- */
80
- onSignupHcaptchaResult?: (data: {
81
- data: SignUpData
82
- /**
83
- * This indicates not only wether the hCaptcha challenge succeeded, but also
84
- * if the score was low enough according to the
85
- * {@link HcaptchaConfig.scoreThreshold}.
86
- *
87
- * @see {@link HCaptchaClient.isAllowed}
88
- */
89
- allowed: boolean
90
- result: HcaptchaVerifyResult
91
- deviceId: DeviceId
92
- deviceMetadata: RequestMetadata
93
- }) => Awaitable<void>
94
-
95
75
  /**
96
76
  * This hook is called when a user attempts to sign up, after every validation
97
77
  * has passed (including hcaptcha).
98
78
  */
99
79
  onSignupAttempt?: (data: {
100
- data: SignUpData
80
+ input: SignUpInput
101
81
  deviceId: DeviceId
102
82
  deviceMetadata: RequestMetadata
103
- hcaptchaResult?: HcaptchaVerifyResult
104
83
  }) => Awaitable<void>
105
84
 
106
85
  /**
@@ -45,7 +45,7 @@ import {
45
45
  } from './account/account-store.js'
46
46
  import { Account } from './account/account.js'
47
47
  import { signInDataSchema } from './account/sign-in-data.js'
48
- import { signUpDataSchema } from './account/sign-up-data.js'
48
+ import { signUpInputSchema } from './account/sign-up-input.js'
49
49
  import { authorizeAssetsMiddleware } from './assets/assets-middleware.js'
50
50
  import { ClientAuth, authJwkThumbprint } from './client/client-auth.js'
51
51
  import {
@@ -1145,28 +1145,27 @@ export class OAuthProvider extends OAuthVerifier {
1145
1145
  * Wrap an OAuth endpoint in a middleware that will set the appropriate
1146
1146
  * response headers and format the response as JSON.
1147
1147
  */
1148
- const jsonHandler = <T, TReq extends Req, TRes extends Res, Json>(
1149
- buildJson: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
1150
- status?: number,
1148
+ const jsonHandler = <T, TReq extends Req, TRes extends Res, Payload>(
1149
+ buildJson: (
1150
+ this: T,
1151
+ req: TReq,
1152
+ res: TRes,
1153
+ ) => Awaitable<{ payload: Payload; status?: number }>,
1151
1154
  ): Handler<T, TReq, TRes> =>
1152
1155
  async function (req, res) {
1153
- try {
1154
- // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
1155
- res.setHeader('Cache-Control', 'no-store')
1156
- res.setHeader('Pragma', 'no-cache')
1157
-
1158
- // Ensure we can agree on a content encoding & type before starting to
1159
- // build the JSON response.
1160
- if (!negotiateContent(req, ['application/json'])) {
1161
- throw createHttpError(406, 'Unsupported media type')
1162
- }
1156
+ // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
1157
+ res.setHeader('Cache-Control', 'no-store')
1158
+ res.setHeader('Pragma', 'no-cache')
1159
+
1160
+ // Ensure we can agree on a content encoding & type before starting to
1161
+ // build the JSON response.
1162
+ if (!negotiateContent(req, ['application/json'])) {
1163
+ throw createHttpError(406, 'Unsupported media type')
1164
+ }
1163
1165
 
1164
- const result = await buildJson.call(this, req, res)
1165
- if (result !== undefined) {
1166
- writeJson(res, result, { status })
1167
- } else if (!res.headersSent) {
1168
- res.writeHead(status ?? 204).end()
1169
- }
1166
+ try {
1167
+ const { payload, status = 200 } = await buildJson.call(this, req, res)
1168
+ writeJson(res, payload, { status })
1170
1169
  } catch (err) {
1171
1170
  onError?.(req, res, err, 'OAuth request error')
1172
1171
 
@@ -1180,13 +1179,13 @@ export class OAuthProvider extends OAuthVerifier {
1180
1179
  }
1181
1180
  }
1182
1181
 
1183
- const oauthHandler = <T, TReq extends Req, TRes extends Res, Json>(
1184
- buildOAuthResponse: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
1182
+ const oauthHandler = <T, TReq extends Req, TRes extends Res, Payload>(
1183
+ buildOAuthResponse: (this: T, req: TReq, res: TRes) => Awaitable<Payload>,
1185
1184
  status?: number,
1186
1185
  ) =>
1187
1186
  combineMiddlewares([
1188
1187
  corsHeaders,
1189
- jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
1188
+ jsonHandler<T, TReq, TRes, Payload>(async function (req, res) {
1190
1189
  try {
1191
1190
  // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
1192
1191
  const dpopNonce = server.nextDpopNonce()
@@ -1196,7 +1195,8 @@ export class OAuthProvider extends OAuthVerifier {
1196
1195
  res.appendHeader('Access-Control-Expose-Headers', name)
1197
1196
  }
1198
1197
 
1199
- return await buildOAuthResponse.call(this, req, res)
1198
+ const payload = await buildOAuthResponse.call(this, req, res)
1199
+ return { payload, status }
1200
1200
  } catch (err) {
1201
1201
  if (!res.headersSent && err instanceof WWWAuthenticateError) {
1202
1202
  const name = 'WWW-Authenticate'
@@ -1206,7 +1206,7 @@ export class OAuthProvider extends OAuthVerifier {
1206
1206
 
1207
1207
  throw err
1208
1208
  }
1209
- }, status),
1209
+ }),
1210
1210
  ])
1211
1211
 
1212
1212
  const apiHandler = <
@@ -1214,7 +1214,7 @@ export class OAuthProvider extends OAuthVerifier {
1214
1214
  TReq extends Req,
1215
1215
  TRes extends Res,
1216
1216
  S extends z.ZodTypeAny,
1217
- Json,
1217
+ Payload,
1218
1218
  >(
1219
1219
  inputSchema: S,
1220
1220
  buildJson: (
@@ -1223,10 +1223,10 @@ export class OAuthProvider extends OAuthVerifier {
1223
1223
  res: TRes,
1224
1224
  input: z.infer<S>,
1225
1225
  context: ApiContext,
1226
- ) => Json | Promise<Json>,
1226
+ ) => Awaitable<Payload>,
1227
1227
  status?: number,
1228
1228
  ) =>
1229
- jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
1229
+ jsonHandler<T, TReq, TRes, Payload>(async function (req, res) {
1230
1230
  validateFetchMode(req, res, ['same-origin'])
1231
1231
  validateFetchSite(req, res, ['same-origin'])
1232
1232
  validateSameOrigin(req, res, issuerOrigin)
@@ -1252,12 +1252,13 @@ export class OAuthProvider extends OAuthVerifier {
1252
1252
  res,
1253
1253
  )
1254
1254
 
1255
- const payload = await parseHttpRequest(req, ['json'])
1256
- const input = await inputSchema.parseAsync(payload, { path: ['body'] })
1255
+ const inputRaw = await parseHttpRequest(req, ['json'])
1256
+ const input = await inputSchema.parseAsync(inputRaw, { path: ['body'] })
1257
1257
 
1258
1258
  const context: ApiContext = { requestUri, deviceId, deviceMetadata }
1259
- return buildJson.call(this, req, res, input, context)
1260
- }, status)
1259
+ const payload = await buildJson.call(this, req, res, input, context)
1260
+ return { payload, status }
1261
+ })
1261
1262
 
1262
1263
  const navigationHandler = <T, TReq extends Req, TRes extends Res>(
1263
1264
  handler: (this: T, req: TReq, res: TRes) => Awaitable<void>,
@@ -1478,6 +1479,8 @@ export class OAuthProvider extends OAuthVerifier {
1478
1479
  } catch (err) {
1479
1480
  onError?.(req, res, err, 'Failed to revoke token')
1480
1481
  }
1482
+
1483
+ return {}
1481
1484
  }),
1482
1485
  )
1483
1486
  router.get(
@@ -1577,14 +1580,15 @@ export class OAuthProvider extends OAuthVerifier {
1577
1580
  apiHandler(
1578
1581
  z.object({ handle: handleSchema }).strict(),
1579
1582
  async function (req, res, data) {
1580
- return server.accountManager.verifyHandleAvailability(data.handle)
1583
+ await server.accountManager.verifyHandleAvailability(data.handle)
1584
+ return { available: true }
1581
1585
  },
1582
1586
  ),
1583
1587
  )
1584
1588
 
1585
1589
  router.post(
1586
1590
  '/oauth/authorize/sign-up',
1587
- apiHandler(signUpDataSchema, async function (req, res, data, ctx) {
1591
+ apiHandler(signUpInputSchema, async function (req, res, data, ctx) {
1588
1592
  return server.signUp(ctx, data)
1589
1593
  }),
1590
1594
  )
@@ -1602,6 +1606,7 @@ export class OAuthProvider extends OAuthVerifier {
1602
1606
  resetPasswordRequestDataSchema,
1603
1607
  async function (req, res, data) {
1604
1608
  await server.accountManager.resetPasswordRequest(data)
1609
+ return { success: true }
1605
1610
  },
1606
1611
  ),
1607
1612
  )
@@ -1612,6 +1617,7 @@ export class OAuthProvider extends OAuthVerifier {
1612
1617
  resetPasswordConfirmDataSchema,
1613
1618
  async function (req, res, data) {
1614
1619
  await server.accountManager.resetPasswordConfirm(data)
1620
+ return { success: true }
1615
1621
  },
1616
1622
  ),
1617
1623
  )
@@ -0,0 +1,18 @@
1
+ import { Html, js } from '../lib/html/index.js'
2
+
3
+ export function declareBackendData(values: Record<string, unknown>): Html {
4
+ return Html.dangerouslyCreate(backendDataGenerator(values))
5
+ }
6
+
7
+ export function* backendDataGenerator(
8
+ values: Record<string, unknown>,
9
+ ): Generator<Html> {
10
+ for (const [key, val] of Object.entries(values)) {
11
+ yield js`window[${key}]=${val};`
12
+ }
13
+ // The script tag is removed after the data is assigned to the global
14
+ // variables to prevent other scripts from reading the values. The "app"
15
+ // script will read the global variable and then unset it. See
16
+ // `readBackendData()` in "src/assets/app/backend-data.ts".
17
+ yield js`document.currentScript.remove();`
18
+ }
@@ -1,7 +1,5 @@
1
- import {
2
- OAuthAuthorizationRequestParameters,
3
- OAuthClientMetadata,
4
- } from '@atproto/oauth-types'
1
+ import type { AuthorizeData, Session } from '@atproto/oauth-provider-api'
2
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
5
3
  import { DeviceAccountInfo } from '../account/account-store.js'
6
4
  import { Account } from '../account/account.js'
7
5
  import { Client } from '../client/client.js'
@@ -30,28 +28,7 @@ export type AuthorizationResultAuthorize = {
30
28
  }
31
29
  }
32
30
 
33
- // TODO: find a way to share this type with the frontend code
34
- // (app/backend-types.ts)
35
-
36
- type Session = {
37
- account: Account
38
- info?: never // Prevent accidental leaks to frontend
39
-
40
- selected: boolean
41
- loginRequired: boolean
42
- consentRequired: boolean
43
- }
44
-
45
- export type AuthorizeData = {
46
- clientId: string
47
- clientMetadata: OAuthClientMetadata
48
- clientTrusted: boolean
49
- requestUri: string
50
- loginHint?: string
51
- scopeDetails?: ScopeDetail[]
52
- newSessionsRequireConsent: boolean
53
- sessions: Session[]
54
- }
31
+ export type { AuthorizeData, Session }
55
32
 
56
33
  export function buildAuthorizeData(
57
34
  data: AuthorizationResultAuthorize,
@@ -1,4 +1,5 @@
1
1
  import { z } from 'zod'
2
+ import { CustomizationData } from '@atproto/oauth-provider-api'
2
3
  import { hcaptchaConfigSchema } from '../lib/hcaptcha.js'
3
4
  import { isLinkRel } from '../lib/html/build-document.js'
4
5
  import { multiLangStringSchema } from '../lib/locale.js'
@@ -59,7 +60,7 @@ export const brandingConfigSchema = z.object({
59
60
  name: z.string().optional(),
60
61
  logo: z.string().optional(),
61
62
  colors: colorsDefinitionSchema.optional(),
62
- links: z.array(linkDefinitionSchema).readonly().optional(),
63
+ links: z.array(linkDefinitionSchema).optional(),
63
64
  })
64
65
  export type BrandingInput = z.input<typeof brandingConfigSchema>
65
66
  export type Branding = z.infer<typeof brandingConfigSchema>
@@ -86,18 +87,6 @@ export const customizationSchema = z.object({
86
87
  export type CustomizationInput = z.input<typeof customizationSchema>
87
88
  export type Customization = z.infer<typeof customizationSchema>
88
89
 
89
- export type CustomizationData = {
90
- // Functional customization
91
- hcaptchaSiteKey?: string
92
- inviteCodeRequired?: boolean
93
- availableUserDomains?: string[]
94
-
95
- // Aesthetic customization
96
- name?: string
97
- logo?: string
98
- links?: readonly LinkDefinition[]
99
- }
100
-
101
90
  export function buildCustomizationData({
102
91
  branding,
103
92
  availableUserDomains,
@@ -0,0 +1,8 @@
1
+ import { ErrorData } from '@atproto/oauth-provider-api'
2
+ import { buildErrorPayload } from './build-error-payload.js'
3
+
4
+ // @NOTE: The primary role of this function is to ensure that the ErrorPayload
5
+ // and ErrorData types are in sync.
6
+ export function buildErrorData(error: unknown): ErrorData {
7
+ return buildErrorPayload(error)
8
+ }
@@ -50,10 +50,12 @@ export function buildErrorStatus(error: unknown): number {
50
50
  return 500
51
51
  }
52
52
 
53
- export function buildErrorPayload(error: unknown): {
53
+ export type ErrorPayload = {
54
54
  error: string
55
55
  error_description: string
56
- } {
56
+ }
57
+
58
+ export function buildErrorPayload(error: unknown): ErrorPayload {
57
59
  if (error instanceof OAuthError) {
58
60
  return error.toJSON()
59
61
  }
@@ -1,8 +1,10 @@
1
1
  import type { ServerResponse } from 'node:http'
2
- import { Asset } from '../assets/asset.js'
3
- import { enumerateAssets } from '../assets/index.js'
2
+ import { CustomizationData } from '@atproto/oauth-provider-api'
3
+ import { assets } from '@atproto/oauth-provider-ui'
4
+ import { buildAssetUrl } from '../assets/assets-middleware.js'
4
5
  import { CspConfig, mergeCsp } from '../lib/csp/index.js'
5
6
  import {
7
+ AssetRef,
6
8
  Html,
7
9
  LinkAttrs,
8
10
  MetaAttrs,
@@ -10,7 +12,9 @@ import {
10
12
  html,
11
13
  isLinkRel,
12
14
  } from '../lib/html/index.js'
15
+ import { CrossOriginEmbedderPolicy } from '../lib/http/security-headers.js'
13
16
  import { AVAILABLE_LOCALES, Locale, isAvailableLocale } from '../lib/locale.js'
17
+ import { declareBackendData } from './backend-data.js'
14
18
  import {
15
19
  AuthorizationResultAuthorize,
16
20
  buildAuthorizeData,
@@ -21,15 +25,28 @@ import {
21
25
  buildCustomizationCss,
22
26
  buildCustomizationData,
23
27
  } from './build-customization-data.js'
24
- import { buildErrorPayload, buildErrorStatus } from './build-error-payload.js'
25
- import { assetToCsp, declareBackendData, sendWebPage } from './send-web-page.js'
28
+ import { buildErrorData } from './build-error-data.js'
29
+ import { buildErrorStatus } from './build-error-payload.js'
30
+ import { sendWebPage } from './send-web-page.js'
31
+
32
+ const BASE_CSP: CspConfig = {
33
+ // API calls are made to the same origin
34
+ 'connect-src': ["'self'"],
35
+ // Allow loading of PDS logo & User avatars
36
+ 'img-src': ['data:', 'https:'],
37
+ // Prevent embedding in iframes
38
+ 'frame-ancestors': ["'none'"],
39
+ }
26
40
 
27
- const HCAPTCHA_CSP = {
41
+ /**
42
+ * @see {@link https://docs.hcaptcha.com/#content-security-policy-settings}
43
+ */
44
+ const HCAPTCHA_CSP: CspConfig = {
28
45
  'script-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
29
46
  'frame-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
30
47
  'style-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
31
48
  'connect-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
32
- } as const satisfies CspConfig
49
+ }
33
50
 
34
51
  export type SendPageOptions = {
35
52
  preferredLocales?: readonly string[]
@@ -41,43 +58,57 @@ export class OutputManager {
41
58
  { name: 'robots', content: 'noindex' },
42
59
  { name: 'description', content: 'ATProto OAuth authorization page' },
43
60
  ]
44
- readonly scripts: readonly (Asset | Html)[]
45
- readonly styles: readonly (Asset | Html)[]
46
61
  readonly csp: CspConfig
62
+ readonly coep: CrossOriginEmbedderPolicy
63
+ readonly customizationData: CustomizationData
64
+ readonly customizationCss: Html
47
65
 
48
66
  constructor(customization: Customization) {
49
67
  this.links = customization.branding?.links
50
68
 
51
- const scripts = Array.from(enumerateAssets('application/javascript'))
52
- const styles = Array.from(enumerateAssets('text/css'))
53
-
54
- // Note: building scripts/styles/csp here for two reasons:
55
- // 1. To avoid re-building it on every request
56
- // 2. To throw during init if the customization/config is invalid
57
-
58
- this.scripts = [
59
- declareBackendData('__availableLocales', AVAILABLE_LOCALES),
60
- declareBackendData(
61
- '__customizationData',
62
- buildCustomizationData(customization),
63
- ),
64
- // Last (to be able to read the "backend data" variables)
65
- ...scripts.filter((asset) => asset.isEntry),
66
- ]
67
-
68
- this.styles = [
69
- // First (to be overridden by customization)
70
- ...styles,
71
- cssCode(buildCustomizationCss(customization)),
72
- ]
73
-
74
- const customizationCsp = customization?.hcaptcha ? HCAPTCHA_CSP : undefined
75
- const assetsCsp: CspConfig = {
76
- 'script-src': scripts.map(assetToCsp),
77
- 'style-src': styles.map(assetToCsp),
78
- }
69
+ // "cache" these:
70
+ this.customizationData = buildCustomizationData(customization)
71
+ this.customizationCss = cssCode(buildCustomizationCss(customization))
79
72
 
80
- this.csp = mergeCsp(customizationCsp, assetsCsp)
73
+ this.csp = mergeCsp(
74
+ BASE_CSP,
75
+ customization?.hcaptcha ? HCAPTCHA_CSP : undefined,
76
+ )
77
+ // Because we are loading avatar images from external sources, that might
78
+ // not have CORP headers set, we need to use at least "credentialless".
79
+ this.coep = customization?.hcaptcha
80
+ ? // https://github.com/hCaptcha/react-hcaptcha/issues/259
81
+ // @TODO Remove the use of `unsafeNone` once the issue above is resolved
82
+ CrossOriginEmbedderPolicy.unsafeNone
83
+ : CrossOriginEmbedderPolicy.credentialless
84
+ }
85
+
86
+ buildAssets(
87
+ name: string,
88
+ backendData: Record<string, unknown>,
89
+ ): {
90
+ scripts: (AssetRef | Html)[]
91
+ styles: (AssetRef | Html)[]
92
+ } {
93
+ return {
94
+ scripts: [
95
+ declareBackendData(backendData),
96
+ // After backend injected data
97
+ ...Array.from(assets)
98
+ .filter(
99
+ ([, item]) =>
100
+ item.type === 'chunk' && item.isEntry && item.name === name,
101
+ )
102
+ .map(([filename]) => ({ url: buildAssetUrl(filename) })),
103
+ ],
104
+ styles: [
105
+ ...Array.from(assets)
106
+ .filter(([, item]) => item.mime === 'text/css')
107
+ .map(([filename]) => ({ url: buildAssetUrl(filename) })),
108
+ // Last (to be able to override the default styles)
109
+ this.customizationCss,
110
+ ],
111
+ }
81
112
  }
82
113
 
83
114
  async sendAuthorizePage(
@@ -89,17 +120,21 @@ export class OutputManager {
89
120
  data.parameters.ui_locales?.split(' ') ?? options?.preferredLocales,
90
121
  )
91
122
 
123
+ const { scripts, styles } = this.buildAssets('authorization-page', {
124
+ __availableLocales: AVAILABLE_LOCALES,
125
+ __customizationData: this.customizationData,
126
+ __authorizeData: buildAuthorizeData(data),
127
+ })
128
+
92
129
  return sendWebPage(res, {
93
- scripts: [
94
- declareBackendData('__authorizeData', buildAuthorizeData(data)),
95
- ...this.scripts,
96
- ],
97
- styles: this.styles,
130
+ scripts,
131
+ styles,
98
132
  meta: this.meta,
99
133
  links: this.buildLinks(locale),
100
134
  htmlAttrs: { lang: locale },
101
135
  body: html`<div id="root"></div>`,
102
136
  csp: this.csp,
137
+ coep: this.coep,
103
138
  })
104
139
  }
105
140
 
@@ -110,18 +145,22 @@ export class OutputManager {
110
145
  ): Promise<void> {
111
146
  const locale = negotiateLocale(options?.preferredLocales)
112
147
 
148
+ const { scripts, styles } = this.buildAssets('error-page', {
149
+ __availableLocales: AVAILABLE_LOCALES,
150
+ __customizationData: this.customizationData,
151
+ __errorData: buildErrorData(err),
152
+ })
153
+
113
154
  return sendWebPage(res, {
114
155
  status: buildErrorStatus(err),
115
- scripts: [
116
- declareBackendData('__errorData', buildErrorPayload(err)),
117
- ...this.scripts,
118
- ],
119
- styles: this.styles,
156
+ scripts,
157
+ styles,
120
158
  meta: this.meta,
121
159
  links: this.buildLinks(locale),
122
160
  htmlAttrs: { lang: locale },
123
161
  body: html`<div id="root"></div>`,
124
162
  csp: this.csp,
163
+ coep: this.coep,
125
164
  })
126
165
  }
127
166