@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
@@ -1,5 +1,4 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http'
2
- import { mediaType } from '@hapi/accept'
3
2
  import createHttpError from 'http-errors'
4
3
  import type { Redis, RedisOptions } from 'ioredis'
5
4
  import { ZodError, z } from 'zod'
@@ -39,14 +38,16 @@ import { AccountManager } from './account/account-manager.js'
39
38
  import {
40
39
  AccountStore,
41
40
  DeviceAccountInfo,
42
- SignInCredentials,
43
41
  asAccountStore,
44
- signInCredentialsSchema,
42
+ handleSchema,
43
+ resetPasswordConfirmDataSchema,
44
+ resetPasswordRequestDataSchema,
45
45
  } from './account/account-store.js'
46
46
  import { Account } from './account/account.js'
47
+ import { signInDataSchema } from './account/sign-in-data.js'
48
+ import { signUpDataSchema } from './account/sign-up-data.js'
47
49
  import { authorizeAssetsMiddleware } from './assets/assets-middleware.js'
48
50
  import { ClientAuth, authJwkThumbprint } from './client/client-auth.js'
49
- import { ClientId, clientIdSchema } from './client/client-id.js'
50
51
  import {
51
52
  ClientManager,
52
53
  LoopbackMetadataGetter,
@@ -55,7 +56,12 @@ import { ClientStore, ifClientStore } from './client/client-store.js'
55
56
  import { Client } from './client/client.js'
56
57
  import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
57
58
  import { DeviceId } from './device/device-id.js'
58
- import { DeviceManager, DeviceManagerOptions } from './device/device-manager.js'
59
+ import {
60
+ DeviceInfo,
61
+ DeviceManager,
62
+ DeviceManagerOptions,
63
+ deviceManagerOptionsSchema,
64
+ } from './device/device-manager.js'
59
65
  import { DeviceStore, asDeviceStore } from './device/device-store.js'
60
66
  import { AccessDeniedError } from './errors/access-denied-error.js'
61
67
  import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
@@ -65,13 +71,14 @@ import { InvalidGrantError } from './errors/invalid-grant-error.js'
65
71
  import { InvalidParametersError } from './errors/invalid-parameters-error.js'
66
72
  import { InvalidRequestError } from './errors/invalid-request-error.js'
67
73
  import { LoginRequiredError } from './errors/login-required-error.js'
68
- import { OAuthError } from './errors/oauth-error.js'
69
74
  import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
70
75
  import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
76
+ import { HcaptchaConfig } from './lib/hcaptcha.js'
71
77
  import {
72
78
  Handler,
73
79
  Middleware,
74
80
  Router,
81
+ cacheControlMiddleware,
75
82
  combineMiddlewares,
76
83
  parseHttpRequest,
77
84
  setupCsrfToken,
@@ -84,18 +91,26 @@ import {
84
91
  validateSameOrigin,
85
92
  writeJson,
86
93
  } from './lib/http/index.js'
87
- import { RequestMetadata } from './lib/http/request.js'
94
+ import {
95
+ RequestMetadata,
96
+ extractLocales,
97
+ negotiateResponseContent as negotiateContent,
98
+ } from './lib/http/request.js'
88
99
  import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
89
- import { Override } from './lib/util/type.js'
100
+ import { Awaitable, Override } from './lib/util/type.js'
90
101
  import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
91
- import { OAuthHooks } from './oauth-hooks.js'
102
+ import { OAuthHooks, SignInData, SignUpData } from './oauth-hooks.js'
92
103
  import { OAuthVerifier, OAuthVerifierOptions } from './oauth-verifier.js'
93
104
  import { AuthorizationResultAuthorize } from './output/build-authorize-data.js'
105
+ import {
106
+ BrandingConfig,
107
+ Customization,
108
+ customizationSchema,
109
+ } from './output/build-customization-data.js'
94
110
  import {
95
111
  buildErrorPayload,
96
112
  buildErrorStatus,
97
113
  } from './output/build-error-payload.js'
98
- import { Customization } from './output/customization.js'
99
114
  import { OutputManager } from './output/output-manager.js'
100
115
  import {
101
116
  AuthorizationResultRedirect,
@@ -114,32 +129,36 @@ import { TokenManager } from './token/token-manager.js'
114
129
  import { TokenStore, asTokenStore } from './token/token-store.js'
115
130
  import { VerifyTokenClaimsOptions } from './token/verify-token-claims.js'
116
131
 
117
- export type OAuthProviderStore = Partial<
118
- ClientStore &
119
- AccountStore &
120
- DeviceStore &
121
- TokenStore &
122
- RequestStore &
123
- ReplayStore
124
- >
125
-
126
132
  export {
133
+ type BrandingConfig,
127
134
  type CustomMetadata,
128
135
  type Customization,
129
136
  type Handler,
137
+ type HcaptchaConfig,
130
138
  Keyset,
131
139
  type OAuthAuthorizationServerMetadata,
132
140
  }
133
141
 
142
+ type ApiContext = {
143
+ requestUri: RequestUri
144
+ deviceId: DeviceId
145
+ deviceMetadata: RequestMetadata
146
+ }
147
+
148
+ export type ErrorHandler<
149
+ Req extends IncomingMessage = IncomingMessage,
150
+ Res extends ServerResponse = ServerResponse,
151
+ > = (req: Req, res: Res, err: unknown, message: string) => void
152
+
134
153
  export type RouterOptions<
135
154
  Req extends IncomingMessage = IncomingMessage,
136
155
  Res extends ServerResponse = ServerResponse,
137
- > = DeviceManagerOptions & {
138
- onError?: (req: Req, res: Res, err: unknown, message: string) => void
156
+ > = {
157
+ onError?: ErrorHandler<Req, Res>
139
158
  }
140
159
 
141
160
  export type OAuthProviderOptions = Override<
142
- OAuthVerifierOptions & OAuthHooks,
161
+ OAuthVerifierOptions & OAuthHooks & DeviceManagerOptions & Customization,
143
162
  {
144
163
  /**
145
164
  * Maximum age a device/account session can be before requiring
@@ -157,11 +176,6 @@ export type OAuthProviderOptions = Override<
157
176
  */
158
177
  metadata?: CustomMetadata
159
178
 
160
- /**
161
- * UI customizations
162
- */
163
- customization?: Customization
164
-
165
179
  /**
166
180
  * A custom fetch function that can be used to fetch the client metadata from
167
181
  * the internet. By default, the fetch function is a safeFetchWrap() function
@@ -184,11 +198,18 @@ export type OAuthProviderOptions = Override<
184
198
  * this store implements all the interfaces not provided in the other
185
199
  * `<name>Store` options.
186
200
  */
187
- store?: OAuthProviderStore
201
+ store?: Partial<
202
+ AccountStore &
203
+ ClientStore &
204
+ DeviceStore &
205
+ ReplayStore &
206
+ RequestStore &
207
+ TokenStore
208
+ >
188
209
 
189
210
  accountStore?: AccountStore
190
- deviceStore?: DeviceStore
191
211
  clientStore?: ClientStore
212
+ deviceStore?: DeviceStore
192
213
  replayStore?: ReplayStore
193
214
  requestStore?: RequestStore
194
215
  tokenStore?: TokenStore
@@ -223,19 +244,18 @@ export type OAuthProviderOptions = Override<
223
244
 
224
245
  export class OAuthProvider extends OAuthVerifier {
225
246
  public readonly metadata: OAuthAuthorizationServerMetadata
226
- public readonly customization?: Customization
227
247
 
228
248
  public readonly authenticationMaxAge: number
229
249
 
230
250
  public readonly accountManager: AccountManager
231
- public readonly deviceStore: DeviceStore
251
+ public readonly deviceManager: DeviceManager
232
252
  public readonly clientManager: ClientManager
233
253
  public readonly requestManager: RequestManager
234
254
  public readonly tokenManager: TokenManager
255
+ public readonly outputManager: OutputManager
235
256
 
236
257
  public constructor({
237
258
  metadata,
238
- customization = undefined,
239
259
  authenticationMaxAge = AUTHENTICATION_MAX_AGE,
240
260
  tokenMaxAge = TOKEN_MAX_AGE,
241
261
 
@@ -264,10 +284,30 @@ export class OAuthProvider extends OAuthVerifier {
264
284
 
265
285
  loopbackMetadata = atprotoLoopbackClientMetadata,
266
286
 
267
- // OAuthHooks & OAuthVerifierOptions
287
+ // OAuthHooks &
288
+ // OAuthVerifierOptions &
289
+ // DeviceManagerOptions &
290
+ // Customization
268
291
  ...rest
269
292
  }: OAuthProviderOptions) {
270
- super({ replayStore, redis, ...rest })
293
+ const customization: Customization = customizationSchema.parse(rest)
294
+ const deviceManagerOptions: DeviceManagerOptions =
295
+ deviceManagerOptionsSchema.parse(rest)
296
+
297
+ // @NOTE: hooks don't really need a type parser, as all zod can actually
298
+ // check at runtime is the fact that the values are functions. The only way
299
+ // we would benefit from zod here would be to wrap the functions with a
300
+ // validator for the provided function's return types, which we do not add
301
+ // because it would impact runtime performance and we trust the users of
302
+ // this lib (basically ourselves) to rely on the typing system to ensure the
303
+ // correct types are returned.
304
+ const hooks: OAuthHooks = rest
305
+
306
+ // @NOTE: validation of super params (if we wanted to implement it) should
307
+ // be the responsibility of the super class.
308
+ const superOptions: OAuthVerifierOptions = rest
309
+
310
+ super({ replayStore, redis, ...superOptions })
271
311
 
272
312
  requestStore ??= redis
273
313
  ? new RequestStoreRedis({ redis })
@@ -275,15 +315,19 @@ export class OAuthProvider extends OAuthVerifier {
275
315
 
276
316
  this.authenticationMaxAge = authenticationMaxAge
277
317
  this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
278
- this.customization = customization
279
-
280
- this.deviceStore = deviceStore
281
318
 
282
- this.accountManager = new AccountManager(accountStore)
319
+ this.deviceManager = new DeviceManager(deviceStore, deviceManagerOptions)
320
+ this.outputManager = new OutputManager(customization)
321
+ this.accountManager = new AccountManager(
322
+ this.issuer,
323
+ accountStore,
324
+ hooks,
325
+ customization,
326
+ )
283
327
  this.clientManager = new ClientManager(
284
328
  this.metadata,
285
329
  this.keyset,
286
- rest,
330
+ hooks,
287
331
  clientStore || null,
288
332
  loopbackMetadata || null,
289
333
  safeFetch,
@@ -294,12 +338,12 @@ export class OAuthProvider extends OAuthVerifier {
294
338
  requestStore,
295
339
  this.signer,
296
340
  this.metadata,
297
- rest,
341
+ hooks,
298
342
  )
299
343
  this.tokenManager = new TokenManager(
300
344
  tokenStore,
301
345
  this.signer,
302
- rest,
346
+ hooks,
303
347
  this.accessTokenType,
304
348
  tokenMaxAge,
305
349
  )
@@ -452,8 +496,7 @@ export class OAuthProvider extends OAuthVerifier {
452
496
  // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1
453
497
  // > Since initial processing of the pushed authorization request does not
454
498
  // > involve resource owner interaction, error codes related to user
455
- // > interaction, such as consent_required defined by [OIDC], are never
456
- // > returned.
499
+ // > interaction, such as "access_denied", are never returned.
457
500
  if (err instanceof AccessDeniedError) {
458
501
  throw new InvalidRequestError(err.error_description, err)
459
502
  }
@@ -471,7 +514,7 @@ export class OAuthProvider extends OAuthVerifier {
471
514
  .parseAsync(query.request_uri, { path: ['query', 'request_uri'] })
472
515
  .catch(throwInvalidRequest)
473
516
 
474
- return this.requestManager.get(requestUri, client.id, deviceId)
517
+ return this.requestManager.get(requestUri, deviceId, client.id)
475
518
  }
476
519
 
477
520
  if ('request' in query) {
@@ -515,11 +558,11 @@ export class OAuthProvider extends OAuthVerifier {
515
558
  }
516
559
 
517
560
  private async deleteRequest(
518
- uri: RequestUri,
561
+ requestUri: RequestUri,
519
562
  parameters: OAuthAuthorizationRequestParameters,
520
563
  ) {
521
564
  try {
522
- await this.requestManager.delete(uri)
565
+ await this.requestManager.delete(requestUri)
523
566
  } catch (err) {
524
567
  throw AccessDeniedError.from(parameters, err)
525
568
  }
@@ -691,24 +734,46 @@ export class OAuthProvider extends OAuthVerifier {
691
734
  }))
692
735
  }
693
736
 
694
- protected async signIn(
695
- deviceId: DeviceId,
696
- uri: RequestUri,
697
- clientId: ClientId,
698
- credentials: SignInCredentials,
737
+ protected async signUp(
738
+ { requestUri, deviceId, deviceMetadata }: ApiContext,
739
+ data: SignUpData,
699
740
  ): Promise<{
700
741
  account: Account
701
742
  consentRequired: boolean
702
743
  }> {
744
+ const { clientId } = await this.requestManager.get(requestUri, deviceId)
745
+
703
746
  const client = await this.clientManager.getClient(clientId)
704
747
 
748
+ const { account } = await this.accountManager.signUp(
749
+ data,
750
+ deviceId,
751
+ deviceMetadata,
752
+ )
753
+
754
+ return {
755
+ account,
756
+ consentRequired: !client.info.isFirstParty,
757
+ }
758
+ }
759
+
760
+ protected async signIn(
761
+ { requestUri, deviceId, deviceMetadata }: ApiContext,
762
+ data: SignInData,
763
+ ): Promise<{
764
+ account: Account
765
+ consentRequired: boolean
766
+ }> {
705
767
  // Ensure the request is still valid (and update the request expiration)
706
768
  // @TODO use the returned scopes to determine if consent is required
707
- await this.requestManager.get(uri, clientId, deviceId)
769
+ const { clientId } = await this.requestManager.get(requestUri, deviceId)
770
+
771
+ const client = await this.clientManager.getClient(clientId)
708
772
 
709
773
  const { account, info } = await this.accountManager.signIn(
710
- credentials,
774
+ data,
711
775
  deviceId,
776
+ deviceMetadata,
712
777
  )
713
778
 
714
779
  return {
@@ -723,22 +788,21 @@ export class OAuthProvider extends OAuthVerifier {
723
788
  }
724
789
 
725
790
  protected async acceptRequest(
726
- uri: RequestUri,
727
- clientId: ClientId,
791
+ { requestUri, deviceId, deviceMetadata }: ApiContext,
728
792
  sub: string,
729
- deviceId: DeviceId,
730
- deviceMetadata: RequestMetadata,
731
793
  ): Promise<AuthorizationResultRedirect> {
732
794
  const { issuer } = this
733
- const client = await this.clientManager.getClient(clientId)
734
795
 
735
- const { parameters, clientAuth } = await this.requestManager.get(
736
- uri,
737
- clientId,
796
+ const { parameters, clientId, clientAuth } = await this.requestManager.get(
797
+ requestUri,
738
798
  deviceId,
739
799
  )
740
800
 
801
+ const client = await this.clientManager.getClient(clientId)
802
+
741
803
  try {
804
+ // @TODO Currently, a user can "accept" a request for any did that sing-in
805
+ // on the device, even if "remember" was set to false.
742
806
  const { account, info } = await this.accountManager.get(deviceId, sub)
743
807
 
744
808
  // The user is trying to authorize without a fresh login
@@ -750,7 +814,7 @@ export class OAuthProvider extends OAuthVerifier {
750
814
  }
751
815
 
752
816
  const code = await this.requestManager.setAuthorized(
753
- uri,
817
+ requestUri,
754
818
  client,
755
819
  account,
756
820
  deviceId,
@@ -766,24 +830,19 @@ export class OAuthProvider extends OAuthVerifier {
766
830
 
767
831
  return { issuer, parameters, redirect: { code } }
768
832
  } catch (err) {
769
- await this.deleteRequest(uri, parameters)
833
+ await this.deleteRequest(requestUri, parameters)
770
834
 
771
835
  throw AccessDeniedError.from(parameters, err)
772
836
  }
773
837
  }
774
838
 
775
- protected async rejectRequest(
776
- deviceId: DeviceId,
777
- uri: RequestUri,
778
- clientId: ClientId,
779
- ): Promise<AuthorizationResultRedirect> {
780
- const { parameters } = await this.requestManager.get(
781
- uri,
782
- clientId,
783
- deviceId,
784
- )
839
+ protected async rejectRequest({
840
+ requestUri,
841
+ deviceId,
842
+ }: ApiContext): Promise<AuthorizationResultRedirect> {
843
+ const { parameters } = await this.requestManager.get(requestUri, deviceId)
785
844
 
786
- await this.deleteRequest(uri, parameters)
845
+ await this.deleteRequest(requestUri, parameters)
787
846
 
788
847
  return {
789
848
  issuer: this.issuer,
@@ -1025,10 +1084,7 @@ export class OAuthProvider extends OAuthVerifier {
1025
1084
  T = void,
1026
1085
  Req extends IncomingMessage = IncomingMessage,
1027
1086
  Res extends ServerResponse = ServerResponse,
1028
- >(options?: RouterOptions<Req, Res>) {
1029
- const deviceManager = new DeviceManager(this.deviceStore, options)
1030
- const outputManager = new OutputManager(this.customization)
1031
-
1087
+ >(options?: RouterOptions<Req, Res>): Router<T, Req, Res> {
1032
1088
  // eslint-disable-next-line @typescript-eslint/no-this-alias
1033
1089
  const server = this
1034
1090
  const issuerUrl = new URL(server.issuer)
@@ -1037,57 +1093,67 @@ export class OAuthProvider extends OAuthVerifier {
1037
1093
 
1038
1094
  // Utils
1039
1095
 
1040
- const csrfCookie = (uri: RequestUri) => `csrf-${uri}`
1041
- const onError =
1096
+ const csrfCookie = (requestUri: RequestUri) => `csrf-${requestUri}`
1097
+ const onError: null | ErrorHandler<Req, Res> =
1042
1098
  options?.onError ??
1043
1099
  (process.env['NODE_ENV'] === 'development'
1044
- ? (req, res, err, msg): void =>
1100
+ ? (req, res, err, msg) => {
1045
1101
  console.error(`OAuthProvider error (${msg}):`, err)
1046
- : undefined)
1102
+ }
1103
+ : null)
1047
1104
 
1048
- /**
1049
- * Creates a middleware that will serve static JSON content.
1050
- */
1051
- const staticJson = (json: unknown): Middleware<void, Req, Res> =>
1052
- combineMiddlewares([
1053
- function (req, res, next) {
1054
- res.setHeader('Access-Control-Allow-Origin', '*')
1055
- res.setHeader('Access-Control-Allow-Headers', '*')
1105
+ // CORS preflight
1106
+ const corsHeaders: Middleware = function (req, res, next) {
1107
+ res.setHeader('Access-Control-Max-Age', '86400') // 1 day
1056
1108
 
1057
- res.setHeader('Cache-Control', 'max-age=300')
1058
- next()
1059
- },
1060
- staticJsonMiddleware(json),
1061
- ])
1109
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
1110
+ //
1111
+ // > For requests without credentials, the literal value "*" can be
1112
+ // > specified as a wildcard; the value tells browsers to allow
1113
+ // > requesting code from any origin to access the resource.
1114
+ // > Attempting to use the wildcard with credentials results in an
1115
+ // > error.
1116
+ //
1117
+ // A "*" is safer to use than reflecting the request origin.
1118
+ res.setHeader('Access-Control-Allow-Origin', '*')
1119
+
1120
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
1121
+ // > The value "*" only counts as a special wildcard value for
1122
+ // > requests without credentials (requests without HTTP cookies or
1123
+ // > HTTP authentication information). In requests with credentials,
1124
+ // > it is treated as the literal method name "*" without special
1125
+ // > semantics.
1126
+ res.setHeader('Access-Control-Allow-Methods', '*')
1127
+
1128
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type,DPoP')
1129
+
1130
+ next()
1131
+ }
1132
+
1133
+ const corsPreflight: Middleware = combineMiddlewares([
1134
+ corsHeaders,
1135
+ (req, res) => {
1136
+ res.writeHead(200).end()
1137
+ },
1138
+ ])
1062
1139
 
1063
1140
  /**
1064
1141
  * Wrap an OAuth endpoint in a middleware that will set the appropriate
1065
1142
  * response headers and format the response as JSON.
1066
1143
  */
1067
1144
  const jsonHandler = <T, TReq extends Req, TRes extends Res, Json>(
1068
- buildJson: (this: T, req: TReq, res: TRes) => Json | Promise<Json>,
1145
+ buildJson: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
1069
1146
  status?: number,
1070
1147
  ): Handler<T, TReq, TRes> =>
1071
1148
  async function (req, res) {
1072
- res.setHeader('Access-Control-Allow-Origin', '*')
1073
- res.setHeader('Access-Control-Allow-Headers', '*')
1074
-
1075
- // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
1076
- res.setHeader('Cache-Control', 'no-store')
1077
- res.setHeader('Pragma', 'no-cache')
1078
-
1079
- // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
1080
- const dpopNonce = server.nextDpopNonce()
1081
- if (dpopNonce) {
1082
- const name = 'DPoP-Nonce'
1083
- res.setHeader(name, dpopNonce)
1084
- res.appendHeader('Access-Control-Expose-Headers', name)
1085
- }
1086
-
1087
1149
  try {
1150
+ // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
1151
+ res.setHeader('Cache-Control', 'no-store')
1152
+ res.setHeader('Pragma', 'no-cache')
1153
+
1088
1154
  // Ensure we can agree on a content encoding & type before starting to
1089
1155
  // build the JSON response.
1090
- if (!mediaType(req.headers['accept'], ['application/json'])) {
1156
+ if (!negotiateContent(req, ['application/json'])) {
1091
1157
  throw createHttpError(406, 'Unsupported media type')
1092
1158
  }
1093
1159
 
@@ -1098,39 +1164,107 @@ export class OAuthProvider extends OAuthVerifier {
1098
1164
  res.writeHead(status ?? 204).end()
1099
1165
  }
1100
1166
  } catch (err) {
1101
- if (!res.headersSent) {
1102
- if (err instanceof WWWAuthenticateError) {
1103
- const name = 'WWW-Authenticate'
1104
- res.setHeader(name, err.wwwAuthenticateHeader)
1105
- res.appendHeader('Access-Control-Expose-Headers', name)
1106
- }
1167
+ onError?.(req, res, err, 'OAuth request error')
1107
1168
 
1169
+ if (!res.headersSent) {
1108
1170
  const payload = buildErrorPayload(err)
1109
1171
  const status = buildErrorStatus(err)
1110
1172
  writeJson(res, payload, { status })
1111
1173
  } else {
1112
1174
  res.destroy()
1113
1175
  }
1114
-
1115
- // OAuthError are used to build expected responses, so we don't log
1116
- // them as errors.
1117
- if (!(err instanceof OAuthError) || err.statusCode >= 500) {
1118
- onError?.(req, res, err, 'Unexpected error')
1119
- }
1120
1176
  }
1121
1177
  }
1122
1178
 
1179
+ const oauthHandler = <T, TReq extends Req, TRes extends Res, Json>(
1180
+ buildOAuthResponse: (this: T, req: TReq, res: TRes) => Awaitable<Json>,
1181
+ status?: number,
1182
+ ) =>
1183
+ combineMiddlewares([
1184
+ corsHeaders,
1185
+ jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
1186
+ try {
1187
+ // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
1188
+ const dpopNonce = server.nextDpopNonce()
1189
+ if (dpopNonce) {
1190
+ const name = 'DPoP-Nonce'
1191
+ res.setHeader(name, dpopNonce)
1192
+ res.appendHeader('Access-Control-Expose-Headers', name)
1193
+ }
1194
+
1195
+ return await buildOAuthResponse.call(this, req, res)
1196
+ } catch (err) {
1197
+ if (!res.headersSent && err instanceof WWWAuthenticateError) {
1198
+ const name = 'WWW-Authenticate'
1199
+ res.setHeader(name, err.wwwAuthenticateHeader)
1200
+ res.appendHeader('Access-Control-Expose-Headers', name)
1201
+ }
1202
+
1203
+ throw err
1204
+ }
1205
+ }, status),
1206
+ ])
1207
+
1208
+ const apiHandler = <
1209
+ T,
1210
+ TReq extends Req,
1211
+ TRes extends Res,
1212
+ S extends z.ZodTypeAny,
1213
+ Json,
1214
+ >(
1215
+ inputSchema: S,
1216
+ buildJson: (
1217
+ this: T,
1218
+ req: TReq,
1219
+ res: TRes,
1220
+ input: z.infer<S>,
1221
+ context: ApiContext,
1222
+ ) => Json | Promise<Json>,
1223
+ status?: number,
1224
+ ) =>
1225
+ jsonHandler<T, TReq, TRes, Json>(async function (req, res) {
1226
+ validateFetchMode(req, res, ['same-origin'])
1227
+ validateFetchSite(req, res, ['same-origin'])
1228
+ validateSameOrigin(req, res, issuerOrigin)
1229
+ const referer = validateReferer(req, res, {
1230
+ origin: issuerOrigin,
1231
+ pathname: '/oauth/authorize',
1232
+ })
1233
+
1234
+ const requestUri = await requestUriSchema.parseAsync(
1235
+ referer.searchParams.get('request_uri'),
1236
+ { path: ['query', 'request_uri'] },
1237
+ )
1238
+
1239
+ validateCsrfToken(
1240
+ req,
1241
+ res,
1242
+ req.headers['x-csrf-token'],
1243
+ csrfCookie(requestUri),
1244
+ )
1245
+
1246
+ const { deviceId, deviceMetadata } = await server.deviceManager.load(
1247
+ req,
1248
+ res,
1249
+ )
1250
+
1251
+ const payload = await parseHttpRequest(req, ['json'])
1252
+ const input = await inputSchema.parseAsync(payload, { path: ['body'] })
1253
+
1254
+ const context: ApiContext = { requestUri, deviceId, deviceMetadata }
1255
+ return buildJson.call(this, req, res, input, context)
1256
+ }, status)
1257
+
1123
1258
  const navigationHandler = <T, TReq extends Req, TRes extends Res>(
1124
- handler: (this: T, req: TReq, res: TRes) => void | Promise<void>,
1259
+ handler: (this: T, req: TReq, res: TRes) => Awaitable<void>,
1125
1260
  ): Handler<T, TReq, TRes> =>
1126
1261
  async function (req, res) {
1127
- res.setHeader('Access-Control-Allow-Origin', '*')
1128
- res.setHeader('Access-Control-Allow-Headers', '*')
1262
+ try {
1263
+ res.setHeader('Cache-Control', 'no-store')
1264
+ res.setHeader('Pragma', 'no-cache')
1129
1265
 
1130
- res.setHeader('Cache-Control', 'no-store')
1131
- res.setHeader('Pragma', 'no-cache')
1266
+ res.setHeader('Referrer-Policy', 'same-origin')
1132
1267
 
1133
- try {
1134
1268
  validateFetchMode(req, res, ['navigate'])
1135
1269
  validateFetchDest(req, res, ['document'])
1136
1270
  validateSameOrigin(req, res, issuerOrigin)
@@ -1145,11 +1279,78 @@ export class OAuthProvider extends OAuthVerifier {
1145
1279
  )
1146
1280
 
1147
1281
  if (!res.headersSent) {
1148
- await outputManager.sendErrorPage(res, err)
1282
+ await server.outputManager.sendErrorPage(res, err, {
1283
+ preferredLocales: extractLocales(req),
1284
+ })
1149
1285
  }
1150
1286
  }
1151
1287
  }
1152
1288
 
1289
+ // Simple GET requests fall under the category of "no-cors" request, meaning
1290
+ // that the browser will allow any cross-origin request, with credentials,
1291
+ // to be sent to the oauth server. The OAuth Server will, however:
1292
+ // 1) validate the request origin (see navigationHandler),
1293
+ // 2) validate the CSRF token,
1294
+ // 3) validate the referer,
1295
+ // 4) validate the sec-fetch-site header,
1296
+ // 4) validate the sec-fetch-mode header (see navigationHandler),
1297
+ // 5) validate the sec-fetch-dest header (see navigationHandler).
1298
+ // And will error (refuse to serve the request) if any of these checks fail.
1299
+ const sameOriginNavigationHandler = <
1300
+ T extends { url: URL },
1301
+ TReq extends Req,
1302
+ TRes extends Res,
1303
+ >(
1304
+ handler: (
1305
+ this: T,
1306
+ req: TReq,
1307
+ res: TRes,
1308
+ deviceInfo: DeviceInfo,
1309
+ ) => Awaitable<void>,
1310
+ ): Handler<T, TReq, TRes> =>
1311
+ navigationHandler(async function (req, res) {
1312
+ validateFetchSite(req, res, ['same-origin'])
1313
+
1314
+ const deviceInfo = await server.deviceManager.load(req, res)
1315
+
1316
+ return handler.call(this, req, res, deviceInfo)
1317
+ })
1318
+
1319
+ const authorizeRedirectNavigationHandler = <
1320
+ T extends { url: URL },
1321
+ TReq extends Req,
1322
+ TRes extends Res,
1323
+ >(
1324
+ handler: (
1325
+ this: T,
1326
+ req: TReq,
1327
+ res: TRes,
1328
+ context: ApiContext,
1329
+ ) => Awaitable<AuthorizationResultRedirect>,
1330
+ ): Handler<T, TReq, TRes> =>
1331
+ sameOriginNavigationHandler(async function (req, res, deviceInfo) {
1332
+ const referer = validateReferer(req, res, {
1333
+ origin: issuerOrigin,
1334
+ pathname: '/oauth/authorize',
1335
+ })
1336
+
1337
+ const requestUri = await requestUriSchema.parseAsync(
1338
+ referer.searchParams.get('request_uri'),
1339
+ )
1340
+
1341
+ const csrfToken = this.url.searchParams.get('csrf_token')
1342
+ const csrfCookieName = csrfCookie(requestUri)
1343
+
1344
+ // Next line will "clear" the CSRF token cookie, preventing replay of
1345
+ // this request (navigating "back" will result in an error).
1346
+ validateCsrfToken(req, res, csrfToken, csrfCookieName, true)
1347
+
1348
+ const context: ApiContext = { ...deviceInfo, requestUri }
1349
+
1350
+ const redirect = await handler.call(this, req, res, context)
1351
+ return sendAuthorizeRedirect(res, redirect)
1352
+ })
1353
+
1153
1354
  /**
1154
1355
  * Provides a better UX when a request is denied by redirecting to the
1155
1356
  * client with the error details. This will also log any error that caused
@@ -1176,44 +1377,26 @@ export class OAuthProvider extends OAuthVerifier {
1176
1377
 
1177
1378
  //- Public OAuth endpoints
1178
1379
 
1380
+ router.options('/.well-known/oauth-authorization-server', corsPreflight)
1179
1381
  router.get(
1180
1382
  '/.well-known/oauth-authorization-server',
1181
- staticJson(server.metadata),
1383
+ corsHeaders,
1384
+ cacheControlMiddleware(300),
1385
+ staticJsonMiddleware(server.metadata),
1182
1386
  )
1183
1387
 
1184
- // CORS preflight
1185
- const corsPreflight: Middleware = function (req, res, _next) {
1186
- res
1187
- .writeHead(204, {
1188
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
1189
- //
1190
- // > For requests without credentials, the literal value "*" can be
1191
- // > specified as a wildcard; the value tells browsers to allow
1192
- // > requesting code from any origin to access the resource.
1193
- // > Attempting to use the wildcard with credentials results in an
1194
- // > error.
1195
- //
1196
- // A "*" is safer to use than reflecting the request origin.
1197
- 'Access-Control-Allow-Origin': '*',
1198
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
1199
- // > The value "*" only counts as a special wildcard value for
1200
- // > requests without credentials (requests without HTTP cookies or
1201
- // > HTTP authentication information). In requests with credentials,
1202
- // > it is treated as the literal method name "*" without special
1203
- // > semantics.
1204
- 'Access-Control-Allow-Methods': '*',
1205
- 'Access-Control-Allow-Headers': 'Content-Type,Authorization,DPoP',
1206
- 'Access-Control-Max-Age': '86400', // 1 day
1207
- })
1208
- .end()
1209
- }
1210
-
1211
- router.get('/oauth/jwks', staticJson(server.jwks))
1388
+ router.options('/oauth/jwks', corsPreflight)
1389
+ router.get(
1390
+ '/oauth/jwks',
1391
+ corsHeaders,
1392
+ cacheControlMiddleware(300),
1393
+ staticJsonMiddleware(server.jwks),
1394
+ )
1212
1395
 
1213
1396
  router.options('/oauth/par', corsPreflight)
1214
1397
  router.post(
1215
1398
  '/oauth/par',
1216
- jsonHandler(async function (req, _res) {
1399
+ oauthHandler(async function (req, _res) {
1217
1400
  const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
1218
1401
 
1219
1402
  const credentials = await oauthClientCredentialsSchema
@@ -1237,11 +1420,9 @@ export class OAuthProvider extends OAuthVerifier {
1237
1420
  )
1238
1421
  }, 201),
1239
1422
  )
1240
-
1241
1423
  // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
1242
1424
  // > If the request did not use the POST method, the authorization server
1243
1425
  // > responds with an HTTP 405 (Method Not Allowed) status code.
1244
- router.options('/oauth/par', corsPreflight)
1245
1426
  router.all('/oauth/par', (req, res) => {
1246
1427
  res.writeHead(405).end()
1247
1428
  })
@@ -1249,10 +1430,11 @@ export class OAuthProvider extends OAuthVerifier {
1249
1430
  router.options('/oauth/token', corsPreflight)
1250
1431
  router.post(
1251
1432
  '/oauth/token',
1252
- jsonHandler(async function (req, _res) {
1433
+ oauthHandler(async function (req, _res) {
1253
1434
  const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
1254
1435
 
1255
- const clientMetadata = await deviceManager.getRequestMetadata(req)
1436
+ const clientMetadata =
1437
+ await server.deviceManager.getRequestMetadata(req)
1256
1438
 
1257
1439
  const clientCredentials = await oauthClientCredentialsSchema
1258
1440
  .parseAsync(payload, { path: ['body'] })
@@ -1280,7 +1462,7 @@ export class OAuthProvider extends OAuthVerifier {
1280
1462
  router.options('/oauth/revoke', corsPreflight)
1281
1463
  router.post(
1282
1464
  '/oauth/revoke',
1283
- jsonHandler(async function (req, res) {
1465
+ oauthHandler(async function (req, res) {
1284
1466
  const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
1285
1467
 
1286
1468
  const tokenIdentification = await oauthTokenIdentificationSchema
@@ -1294,8 +1476,6 @@ export class OAuthProvider extends OAuthVerifier {
1294
1476
  }
1295
1477
  }),
1296
1478
  )
1297
-
1298
- router.options('/oauth/revoke', corsPreflight)
1299
1479
  router.get(
1300
1480
  '/oauth/revoke',
1301
1481
  navigationHandler(async function (req, res) {
@@ -1320,9 +1500,10 @@ export class OAuthProvider extends OAuthVerifier {
1320
1500
  }),
1321
1501
  )
1322
1502
 
1503
+ router.options('/oauth/introspect', corsPreflight)
1323
1504
  router.post(
1324
1505
  '/oauth/introspect',
1325
- jsonHandler(async function (req, _res) {
1506
+ oauthHandler(async function (req, _res) {
1326
1507
  const payload = await parseHttpRequest(req, ['json', 'urlencoded'])
1327
1508
 
1328
1509
  const credentials = await oauthClientCredentialsSchema
@@ -1349,7 +1530,7 @@ export class OAuthProvider extends OAuthVerifier {
1349
1530
  const query = Object.fromEntries(this.url.searchParams)
1350
1531
 
1351
1532
  const clientCredentials = await oauthClientCredentialsSchema
1352
- .parseAsync(query, { path: ['body'] })
1533
+ .parseAsync(query, { path: ['query'] })
1353
1534
  .catch(throwInvalidRequest)
1354
1535
 
1355
1536
  if ('client_secret' in clientCredentials) {
@@ -1360,9 +1541,14 @@ export class OAuthProvider extends OAuthVerifier {
1360
1541
  .parseAsync(query, { path: ['query'] })
1361
1542
  .catch(throwInvalidRequest)
1362
1543
 
1363
- const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
1544
+ const { deviceId, deviceMetadata } = await server.deviceManager.load(
1545
+ req,
1546
+ res,
1547
+ )
1364
1548
 
1365
- const data = await server
1549
+ const result:
1550
+ | AuthorizationResultRedirect
1551
+ | AuthorizationResultAuthorize = await server
1366
1552
  .authorize(
1367
1553
  clientCredentials,
1368
1554
  authorizationRequest,
@@ -1371,170 +1557,79 @@ export class OAuthProvider extends OAuthVerifier {
1371
1557
  )
1372
1558
  .catch((err) => accessDeniedToRedirectCatcher(req, res, err))
1373
1559
 
1374
- switch (true) {
1375
- case 'redirect' in data: {
1376
- return sendAuthorizeRedirect(res, data)
1377
- }
1378
- case 'authorize' in data: {
1379
- await setupCsrfToken(req, res, csrfCookie(data.authorize.uri))
1380
- return outputManager.sendAuthorizePage(res, data)
1381
- }
1382
- default: {
1383
- // Should never happen
1384
- throw new Error('Unexpected authorization result')
1385
- }
1560
+ if ('redirect' in result) {
1561
+ return sendAuthorizeRedirect(res, result)
1562
+ } else {
1563
+ await setupCsrfToken(req, res, csrfCookie(result.authorize.uri))
1564
+ return server.outputManager.sendAuthorizePage(res, result, {
1565
+ preferredLocales: extractLocales(req),
1566
+ })
1386
1567
  }
1387
1568
  }),
1388
1569
  )
1389
1570
 
1390
- const signInPayloadSchema = z.object({
1391
- csrf_token: z.string(),
1392
- request_uri: requestUriSchema,
1393
- client_id: clientIdSchema,
1394
- credentials: signInCredentialsSchema,
1395
- })
1396
-
1397
- router.options('/oauth/authorize/sign-in', corsPreflight)
1398
1571
  router.post(
1399
- '/oauth/authorize/sign-in',
1400
- jsonHandler(async function (req, res) {
1401
- validateFetchMode(req, res, ['same-origin'])
1402
- validateFetchSite(req, res, ['same-origin'])
1403
- validateSameOrigin(req, res, issuerOrigin)
1404
-
1405
- const payload = await parseHttpRequest(req, ['json'])
1406
- const input = await signInPayloadSchema.parseAsync(payload, {
1407
- path: ['body'],
1408
- })
1409
-
1410
- validateReferer(req, res, {
1411
- origin: issuerOrigin,
1412
- pathname: '/oauth/authorize',
1413
- })
1414
- validateCsrfToken(
1415
- req,
1416
- res,
1417
- input.csrf_token,
1418
- csrfCookie(input.request_uri),
1419
- )
1572
+ '/oauth/authorize/verify-handle-availability',
1573
+ apiHandler(
1574
+ z.object({ handle: handleSchema }).strict(),
1575
+ async function (req, res, data) {
1576
+ return server.accountManager.verifyHandleAvailability(data.handle)
1577
+ },
1578
+ ),
1579
+ )
1420
1580
 
1421
- const { deviceId } = await deviceManager.load(req, res, true)
1581
+ router.post(
1582
+ '/oauth/authorize/sign-up',
1583
+ apiHandler(signUpDataSchema, async function (req, res, data, ctx) {
1584
+ return server.signUp(ctx, data)
1585
+ }),
1586
+ )
1422
1587
 
1423
- return server.signIn(
1424
- deviceId,
1425
- input.request_uri,
1426
- input.client_id,
1427
- input.credentials,
1428
- )
1588
+ router.post(
1589
+ '/oauth/authorize/sign-in',
1590
+ apiHandler(signInDataSchema, async function (req, res, data, ctx) {
1591
+ return server.signIn(ctx, data)
1429
1592
  }),
1430
1593
  )
1431
1594
 
1432
- const acceptQuerySchema = z.object({
1433
- csrf_token: z.string(),
1434
- request_uri: requestUriSchema,
1435
- client_id: clientIdSchema,
1436
- account_sub: z.string(),
1437
- })
1595
+ router.post(
1596
+ '/oauth/authorize/reset-password-request',
1597
+ apiHandler(
1598
+ resetPasswordRequestDataSchema,
1599
+ async function (req, res, data) {
1600
+ await server.accountManager.resetPasswordRequest(data)
1601
+ },
1602
+ ),
1603
+ )
1604
+
1605
+ router.post(
1606
+ '/oauth/authorize/reset-password-confirm',
1607
+ apiHandler(
1608
+ resetPasswordConfirmDataSchema,
1609
+ async function (req, res, data) {
1610
+ await server.accountManager.resetPasswordConfirm(data)
1611
+ },
1612
+ ),
1613
+ )
1438
1614
 
1439
- // Though this is a "no-cors" request, meaning that the browser will allow
1440
- // any cross-origin request, with credentials, to be sent, the handler will
1441
- // 1) validate the request origin,
1442
- // 2) validate the CSRF token,
1443
- // 3) validate the referer,
1444
- // 4) validate the sec-fetch-site header,
1445
- // 4) validate the sec-fetch-mode header,
1446
- // 5) validate the sec-fetch-dest header (see navigationHandler).
1447
- // And will error if any of these checks fail.
1448
1615
  router.get(
1449
1616
  '/oauth/authorize/accept',
1450
- navigationHandler(async function (req, res) {
1451
- validateFetchSite(req, res, ['same-origin'])
1452
-
1453
- const query = Object.fromEntries(this.url.searchParams)
1454
- const input = await acceptQuerySchema.parseAsync(query, {
1455
- path: ['query'],
1456
- })
1457
-
1458
- validateReferer(req, res, {
1459
- origin: issuerOrigin,
1460
- pathname: '/oauth/authorize',
1461
- searchParams: [
1462
- ['request_uri', input.request_uri],
1463
- ['client_id', input.client_id],
1464
- ],
1465
- })
1466
- validateCsrfToken(
1467
- req,
1468
- res,
1469
- input.csrf_token,
1470
- csrfCookie(input.request_uri),
1471
- true,
1472
- )
1473
-
1474
- const { deviceId, deviceMetadata } = await deviceManager.load(req, res)
1617
+ authorizeRedirectNavigationHandler(async function (req, res, ctx) {
1618
+ const sub = this.url.searchParams.get('account_sub')
1619
+ if (!sub) throw new InvalidRequestError('Account sub not provided')
1475
1620
 
1476
- const data = await server
1477
- .acceptRequest(
1478
- input.request_uri,
1479
- input.client_id,
1480
- input.account_sub,
1481
- deviceId,
1482
- deviceMetadata,
1483
- )
1621
+ return server
1622
+ .acceptRequest(ctx, sub)
1484
1623
  .catch((err) => accessDeniedToRedirectCatcher(req, res, err))
1485
-
1486
- return await sendAuthorizeRedirect(res, data)
1487
1624
  }),
1488
1625
  )
1489
1626
 
1490
- const rejectQuerySchema = z.object({
1491
- csrf_token: z.string(),
1492
- request_uri: requestUriSchema,
1493
- client_id: clientIdSchema,
1494
- })
1495
-
1496
- // Though this is a "no-cors" request, meaning that the browser will allow
1497
- // any cross-origin request, with credentials, to be sent, the handler will
1498
- // 1) validate the request origin,
1499
- // 2) validate the CSRF token,
1500
- // 3) validate the referer,
1501
- // 4) validate the sec-fetch-site header,
1502
- // 4) validate the sec-fetch-mode header,
1503
- // 5) validate the sec-fetch-dest header (see navigationHandler).
1504
- // And will error if any of these checks fail.
1505
1627
  router.get(
1506
1628
  '/oauth/authorize/reject',
1507
- navigationHandler(async function (req, res) {
1508
- validateFetchSite(req, res, ['same-origin'])
1509
-
1510
- const query = Object.fromEntries(this.url.searchParams)
1511
- const input = await rejectQuerySchema.parseAsync(query, {
1512
- path: ['query'],
1513
- })
1514
-
1515
- validateReferer(req, res, {
1516
- origin: issuerOrigin,
1517
- pathname: '/oauth/authorize',
1518
- searchParams: [
1519
- ['request_uri', input.request_uri],
1520
- ['client_id', input.client_id],
1521
- ],
1522
- })
1523
- validateCsrfToken(
1524
- req,
1525
- res,
1526
- input.csrf_token,
1527
- csrfCookie(input.request_uri),
1528
- true,
1529
- )
1530
-
1531
- const { deviceId } = await deviceManager.load(req, res)
1532
-
1533
- const data = await server
1534
- .rejectRequest(deviceId, input.request_uri, input.client_id)
1629
+ authorizeRedirectNavigationHandler(async function (req, res, ctx) {
1630
+ return server
1631
+ .rejectRequest(ctx)
1535
1632
  .catch((err) => accessDeniedToRedirectCatcher(req, res, err))
1536
-
1537
- return await sendAuthorizeRedirect(res, data)
1538
1633
  }),
1539
1634
  )
1540
1635