@atproto/oauth-provider 0.6.6 → 0.7.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 (465) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/access-token/access-token-mode.d.ts +5 -0
  3. package/dist/access-token/access-token-mode.d.ts.map +1 -0
  4. package/dist/access-token/access-token-mode.js +9 -0
  5. package/dist/access-token/access-token-mode.js.map +1 -0
  6. package/dist/account/account-manager.d.ts +13 -7
  7. package/dist/account/account-manager.d.ts.map +1 -1
  8. package/dist/account/account-manager.js +69 -52
  9. package/dist/account/account-manager.js.map +1 -1
  10. package/dist/account/account-store.d.ts +88 -77
  11. package/dist/account/account-store.d.ts.map +1 -1
  12. package/dist/account/account-store.js +24 -73
  13. package/dist/account/account-store.js.map +1 -1
  14. package/dist/account/sign-in-data.d.ts +4 -13
  15. package/dist/account/sign-in-data.d.ts.map +1 -1
  16. package/dist/account/sign-in-data.js +9 -9
  17. package/dist/account/sign-in-data.js.map +1 -1
  18. package/dist/account/sign-up-input.d.ts +4 -5
  19. package/dist/account/sign-up-input.d.ts.map +1 -1
  20. package/dist/account/sign-up-input.js +13 -3
  21. package/dist/account/sign-up-input.js.map +1 -1
  22. package/dist/client/client-manager.d.ts +4 -1
  23. package/dist/client/client-manager.d.ts.map +1 -1
  24. package/dist/client/client-manager.js +13 -1
  25. package/dist/client/client-manager.js.map +1 -1
  26. package/dist/client/client-store.d.ts +1 -1
  27. package/dist/client/client-store.d.ts.map +1 -1
  28. package/dist/constants.d.ts +5 -1
  29. package/dist/constants.d.ts.map +1 -1
  30. package/dist/constants.js +6 -2
  31. package/dist/constants.js.map +1 -1
  32. package/dist/customization/branding.d.ts +54 -0
  33. package/dist/customization/branding.d.ts.map +1 -0
  34. package/dist/customization/branding.js +13 -0
  35. package/dist/customization/branding.js.map +1 -0
  36. package/dist/customization/build-customization-css.d.ts +3 -0
  37. package/dist/customization/build-customization-css.d.ts.map +1 -0
  38. package/dist/customization/build-customization-css.js +27 -0
  39. package/dist/customization/build-customization-css.js.map +1 -0
  40. package/dist/customization/build-customization-data.d.ts +4 -0
  41. package/dist/customization/build-customization-data.d.ts.map +1 -0
  42. package/dist/customization/build-customization-data.js +18 -0
  43. package/dist/customization/build-customization-data.js.map +1 -0
  44. package/dist/customization/colors.d.ts +7 -0
  45. package/dist/customization/colors.d.ts.map +1 -0
  46. package/dist/customization/colors.js +27 -0
  47. package/dist/customization/colors.js.map +1 -0
  48. package/dist/customization/customization.d.ts +129 -0
  49. package/dist/customization/customization.d.ts.map +1 -0
  50. package/dist/customization/customization.js +26 -0
  51. package/dist/customization/customization.js.map +1 -0
  52. package/dist/customization/links.d.ts +26 -0
  53. package/dist/customization/links.d.ts.map +1 -0
  54. package/dist/customization/links.js +12 -0
  55. package/dist/customization/links.js.map +1 -0
  56. package/dist/device/device-id.d.ts +1 -0
  57. package/dist/device/device-id.d.ts.map +1 -1
  58. package/dist/device/device-id.js +4 -0
  59. package/dist/device/device-id.js.map +1 -1
  60. package/dist/device/device-manager.d.ts +6 -36
  61. package/dist/device/device-manager.d.ts.map +1 -1
  62. package/dist/device/device-manager.js +49 -43
  63. package/dist/device/device-manager.js.map +1 -1
  64. package/dist/device/device-store.d.ts +1 -0
  65. package/dist/device/device-store.d.ts.map +1 -1
  66. package/dist/device/device-store.js.map +1 -1
  67. package/dist/dpop/dpop-manager.d.ts +3 -3
  68. package/dist/dpop/dpop-nonce.d.ts +3 -3
  69. package/dist/dpop/dpop-nonce.d.ts.map +1 -1
  70. package/dist/errors/access-denied-error.d.ts +4 -3
  71. package/dist/errors/access-denied-error.d.ts.map +1 -1
  72. package/dist/errors/access-denied-error.js +5 -6
  73. package/dist/errors/access-denied-error.js.map +1 -1
  74. package/dist/{output/build-error-payload.d.ts → errors/error-parser.d.ts} +1 -1
  75. package/dist/errors/error-parser.d.ts.map +1 -0
  76. package/dist/{output/build-error-payload.js → errors/error-parser.js} +2 -2
  77. package/dist/errors/error-parser.js.map +1 -0
  78. package/dist/errors/invalid-grant-error.d.ts +1 -0
  79. package/dist/errors/invalid-grant-error.d.ts.map +1 -1
  80. package/dist/errors/invalid-grant-error.js +5 -0
  81. package/dist/errors/invalid-grant-error.js.map +1 -1
  82. package/dist/errors/login-required-error.d.ts +1 -0
  83. package/dist/errors/login-required-error.d.ts.map +1 -1
  84. package/dist/errors/login-required-error.js +5 -0
  85. package/dist/errors/login-required-error.js.map +1 -1
  86. package/dist/index.d.ts +1 -0
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1 -0
  89. package/dist/index.js.map +1 -1
  90. package/dist/lib/html/build-document.d.ts +2 -2
  91. package/dist/lib/html/build-document.d.ts.map +1 -1
  92. package/dist/lib/html/build-document.js +4 -0
  93. package/dist/lib/html/build-document.js.map +1 -1
  94. package/dist/lib/html/hydration-data.d.ts +4 -0
  95. package/dist/lib/html/hydration-data.d.ts.map +1 -0
  96. package/dist/{output/backend-data.js → lib/html/hydration-data.js} +8 -8
  97. package/dist/lib/html/hydration-data.js.map +1 -0
  98. package/dist/lib/html/tags.d.ts +1 -1
  99. package/dist/lib/html/tags.d.ts.map +1 -1
  100. package/dist/lib/html/tags.js +1 -1
  101. package/dist/lib/html/tags.js.map +1 -1
  102. package/dist/lib/http/accept.d.ts +2 -2
  103. package/dist/lib/http/accept.d.ts.map +1 -1
  104. package/dist/lib/http/accept.js +1 -1
  105. package/dist/lib/http/accept.js.map +1 -1
  106. package/dist/lib/http/context.d.ts +2 -4
  107. package/dist/lib/http/context.d.ts.map +1 -1
  108. package/dist/lib/http/context.js +29 -4
  109. package/dist/lib/http/context.js.map +1 -1
  110. package/dist/lib/http/headers.d.ts +3 -0
  111. package/dist/lib/http/headers.d.ts.map +1 -0
  112. package/dist/lib/http/headers.js +14 -0
  113. package/dist/lib/http/headers.js.map +1 -0
  114. package/dist/lib/http/index.d.ts +1 -0
  115. package/dist/lib/http/index.d.ts.map +1 -1
  116. package/dist/lib/http/index.js +1 -0
  117. package/dist/lib/http/index.js.map +1 -1
  118. package/dist/lib/http/middleware.d.ts +1 -1
  119. package/dist/lib/http/middleware.d.ts.map +1 -1
  120. package/dist/lib/http/middleware.js +8 -24
  121. package/dist/lib/http/middleware.js.map +1 -1
  122. package/dist/lib/http/parser.d.ts +3 -3
  123. package/dist/lib/http/parser.d.ts.map +1 -1
  124. package/dist/lib/http/request.d.ts +13 -9
  125. package/dist/lib/http/request.d.ts.map +1 -1
  126. package/dist/lib/http/request.js +27 -49
  127. package/dist/lib/http/request.js.map +1 -1
  128. package/dist/lib/http/response.d.ts +6 -2
  129. package/dist/lib/http/response.d.ts.map +1 -1
  130. package/dist/lib/http/response.js +31 -11
  131. package/dist/lib/http/response.js.map +1 -1
  132. package/dist/lib/http/route.d.ts +3 -3
  133. package/dist/lib/http/route.d.ts.map +1 -1
  134. package/dist/lib/http/route.js +1 -1
  135. package/dist/lib/http/route.js.map +1 -1
  136. package/dist/lib/http/router.d.ts +12 -11
  137. package/dist/lib/http/router.d.ts.map +1 -1
  138. package/dist/lib/http/router.js +26 -34
  139. package/dist/lib/http/router.js.map +1 -1
  140. package/dist/lib/http/security-headers.js +1 -1
  141. package/dist/lib/http/security-headers.js.map +1 -1
  142. package/dist/lib/http/stream.d.ts +3 -3
  143. package/dist/lib/http/stream.d.ts.map +1 -1
  144. package/dist/lib/http/types.d.ts +1 -1
  145. package/dist/lib/http/types.d.ts.map +1 -1
  146. package/dist/lib/send-web-page.d.ts +8 -0
  147. package/dist/lib/send-web-page.d.ts.map +1 -0
  148. package/dist/{output → lib}/send-web-page.js +9 -7
  149. package/dist/lib/send-web-page.js.map +1 -0
  150. package/dist/lib/util/authorization-header.d.ts.map +1 -1
  151. package/dist/lib/util/color.d.ts +32 -0
  152. package/dist/lib/util/color.d.ts.map +1 -0
  153. package/dist/lib/util/color.js +116 -0
  154. package/dist/lib/util/color.js.map +1 -0
  155. package/dist/lib/util/crypto.d.ts +1 -0
  156. package/dist/lib/util/crypto.d.ts.map +1 -1
  157. package/dist/lib/util/crypto.js +8 -3
  158. package/dist/lib/util/crypto.js.map +1 -1
  159. package/dist/lib/util/function.d.ts +1 -0
  160. package/dist/lib/util/function.d.ts.map +1 -1
  161. package/dist/lib/util/function.js +12 -0
  162. package/dist/lib/util/function.js.map +1 -1
  163. package/dist/lib/util/locale.d.ts +20 -0
  164. package/dist/lib/util/locale.d.ts.map +1 -0
  165. package/dist/lib/util/locale.js +14 -0
  166. package/dist/lib/util/locale.js.map +1 -0
  167. package/dist/lib/util/time.d.ts +1 -1
  168. package/dist/lib/util/time.d.ts.map +1 -1
  169. package/dist/lib/util/time.js +1 -1
  170. package/dist/lib/util/time.js.map +1 -1
  171. package/dist/lib/util/type.d.ts +22 -0
  172. package/dist/lib/util/type.d.ts.map +1 -1
  173. package/dist/lib/util/type.js.map +1 -1
  174. package/dist/lib/util/ui8.d.ts +4 -0
  175. package/dist/lib/util/ui8.d.ts.map +1 -0
  176. package/dist/lib/util/ui8.js +17 -0
  177. package/dist/lib/util/ui8.js.map +1 -0
  178. package/dist/lib/util/zod-error.d.ts +2 -0
  179. package/dist/lib/util/zod-error.d.ts.map +1 -0
  180. package/dist/lib/util/zod-error.js +16 -0
  181. package/dist/lib/util/zod-error.js.map +1 -0
  182. package/dist/oauth-errors.d.ts +22 -22
  183. package/dist/oauth-errors.d.ts.map +1 -1
  184. package/dist/oauth-errors.js +37 -45
  185. package/dist/oauth-errors.js.map +1 -1
  186. package/dist/oauth-hooks.d.ts +11 -23
  187. package/dist/oauth-hooks.d.ts.map +1 -1
  188. package/dist/oauth-hooks.js.map +1 -1
  189. package/dist/oauth-middleware.d.ts +12 -0
  190. package/dist/oauth-middleware.d.ts.map +1 -0
  191. package/dist/oauth-middleware.js +32 -0
  192. package/dist/oauth-middleware.js.map +1 -0
  193. package/dist/oauth-provider.d.ts +109 -113
  194. package/dist/oauth-provider.d.ts.map +1 -1
  195. package/dist/oauth-provider.js +124 -542
  196. package/dist/oauth-provider.js.map +1 -1
  197. package/dist/oauth-verifier.d.ts +7 -26
  198. package/dist/oauth-verifier.d.ts.map +1 -1
  199. package/dist/oauth-verifier.js +6 -16
  200. package/dist/oauth-verifier.js.map +1 -1
  201. package/dist/request/code.d.ts.map +1 -1
  202. package/dist/request/request-data.d.ts +2 -4
  203. package/dist/request/request-data.d.ts.map +1 -1
  204. package/dist/request/request-data.js.map +1 -1
  205. package/dist/request/request-manager.d.ts +4 -2
  206. package/dist/request/request-manager.d.ts.map +1 -1
  207. package/dist/request/request-manager.js +9 -8
  208. package/dist/request/request-manager.js.map +1 -1
  209. package/dist/request/request-store.d.ts +6 -0
  210. package/dist/request/request-store.d.ts.map +1 -1
  211. package/dist/request/request-store.js +3 -1
  212. package/dist/request/request-store.js.map +1 -1
  213. package/dist/result/authorization-redirect-parameters.d.ts +18 -0
  214. package/dist/result/authorization-redirect-parameters.d.ts.map +1 -0
  215. package/dist/result/authorization-redirect-parameters.js +3 -0
  216. package/dist/result/authorization-redirect-parameters.js.map +1 -0
  217. package/dist/result/authorization-result-authorize-page.d.ts +13 -0
  218. package/dist/result/authorization-result-authorize-page.d.ts.map +1 -0
  219. package/dist/result/authorization-result-authorize-page.js +3 -0
  220. package/dist/result/authorization-result-authorize-page.js.map +1 -0
  221. package/dist/result/authorization-result-redirect.d.ts +8 -0
  222. package/dist/result/authorization-result-redirect.d.ts.map +1 -0
  223. package/dist/result/authorization-result-redirect.js +3 -0
  224. package/dist/result/authorization-result-redirect.js.map +1 -0
  225. package/dist/router/assets/assets-manifest.d.ts +10 -0
  226. package/dist/router/assets/assets-manifest.d.ts.map +1 -0
  227. package/dist/router/assets/assets-manifest.js +77 -0
  228. package/dist/router/assets/assets-manifest.js.map +1 -0
  229. package/dist/router/assets/assets.d.ts +16 -0
  230. package/dist/router/assets/assets.d.ts.map +1 -0
  231. package/dist/router/assets/assets.js +43 -0
  232. package/dist/router/assets/assets.js.map +1 -0
  233. package/dist/router/assets/csrf.d.ts +4 -0
  234. package/dist/router/assets/csrf.d.ts.map +1 -0
  235. package/dist/router/assets/csrf.js +51 -0
  236. package/dist/router/assets/csrf.js.map +1 -0
  237. package/dist/router/assets/send-account-page.d.ts +7 -0
  238. package/dist/router/assets/send-account-page.d.ts.map +1 -0
  239. package/dist/router/assets/send-account-page.js +34 -0
  240. package/dist/router/assets/send-account-page.js.map +1 -0
  241. package/dist/router/assets/send-authorization-page.d.ts +5 -0
  242. package/dist/router/assets/send-authorization-page.d.ts.map +1 -0
  243. package/dist/router/assets/send-authorization-page.js +49 -0
  244. package/dist/router/assets/send-authorization-page.js.map +1 -0
  245. package/dist/router/assets/send-error-page.d.ts +4 -0
  246. package/dist/router/assets/send-error-page.d.ts.map +1 -0
  247. package/dist/router/assets/send-error-page.js +34 -0
  248. package/dist/router/assets/send-error-page.js.map +1 -0
  249. package/dist/router/create-account-page-middleware.d.ts +6 -0
  250. package/dist/router/create-account-page-middleware.d.ts.map +1 -0
  251. package/dist/router/create-account-page-middleware.js +39 -0
  252. package/dist/router/create-account-page-middleware.js.map +1 -0
  253. package/dist/router/create-api-middleware.d.ts +8 -0
  254. package/dist/router/create-api-middleware.d.ts.map +1 -0
  255. package/dist/router/create-api-middleware.js +501 -0
  256. package/dist/router/create-api-middleware.js.map +1 -0
  257. package/dist/router/create-authorization-page-middleware.d.ts +6 -0
  258. package/dist/router/create-authorization-page-middleware.d.ts.map +1 -0
  259. package/dist/router/create-authorization-page-middleware.js +104 -0
  260. package/dist/router/create-authorization-page-middleware.js.map +1 -0
  261. package/dist/router/create-oauth-middleware.d.ts +6 -0
  262. package/dist/router/create-oauth-middleware.d.ts.map +1 -0
  263. package/dist/router/create-oauth-middleware.js +142 -0
  264. package/dist/router/create-oauth-middleware.js.map +1 -0
  265. package/dist/router/error-handler.d.ts +3 -0
  266. package/dist/router/error-handler.d.ts.map +1 -0
  267. package/dist/{account/account.js → router/error-handler.js} +1 -1
  268. package/dist/router/error-handler.js.map +1 -0
  269. package/dist/router/middleware-options.d.ts +6 -0
  270. package/dist/router/middleware-options.d.ts.map +1 -0
  271. package/dist/router/middleware-options.js +3 -0
  272. package/dist/router/middleware-options.js.map +1 -0
  273. package/dist/router/send-redirect.d.ts +16 -0
  274. package/dist/router/send-redirect.d.ts.map +1 -0
  275. package/dist/{output/send-authorize-redirect.js → router/send-redirect.js} +40 -24
  276. package/dist/router/send-redirect.js.map +1 -0
  277. package/dist/{token/token-claims.d.ts → signer/api-token-payload.d.ts} +237 -232
  278. package/dist/signer/api-token-payload.d.ts.map +1 -0
  279. package/dist/signer/api-token-payload.js +17 -0
  280. package/dist/signer/api-token-payload.js.map +1 -0
  281. package/dist/signer/signed-token-payload.d.ts +164 -159
  282. package/dist/signer/signed-token-payload.d.ts.map +1 -1
  283. package/dist/signer/signed-token-payload.js +10 -16
  284. package/dist/signer/signed-token-payload.js.map +1 -1
  285. package/dist/signer/signer.d.ts +42 -11246
  286. package/dist/signer/signer.d.ts.map +1 -1
  287. package/dist/signer/signer.js +30 -15
  288. package/dist/signer/signer.js.map +1 -1
  289. package/dist/token/refresh-token.d.ts.map +1 -1
  290. package/dist/token/token-data.d.ts +1 -1
  291. package/dist/token/token-data.d.ts.map +1 -1
  292. package/dist/token/token-id.d.ts.map +1 -1
  293. package/dist/token/token-manager.d.ts +28 -26
  294. package/dist/token/token-manager.d.ts.map +1 -1
  295. package/dist/token/token-manager.js +138 -196
  296. package/dist/token/token-manager.js.map +1 -1
  297. package/dist/token/token-store.d.ts +4 -4
  298. package/dist/token/token-store.d.ts.map +1 -1
  299. package/dist/token/token-store.js +1 -0
  300. package/dist/token/token-store.js.map +1 -1
  301. package/dist/token/verify-token-claims.d.ts +3 -3
  302. package/dist/token/verify-token-claims.d.ts.map +1 -1
  303. package/dist/token/verify-token-claims.js +1 -1
  304. package/dist/token/verify-token-claims.js.map +1 -1
  305. package/dist/types/email-otp.d.ts +3 -0
  306. package/dist/types/email-otp.d.ts.map +1 -0
  307. package/dist/types/email-otp.js +6 -0
  308. package/dist/types/email-otp.js.map +1 -0
  309. package/dist/types/email.d.ts +3 -0
  310. package/dist/types/email.d.ts.map +1 -0
  311. package/dist/types/email.js +29 -0
  312. package/dist/types/email.js.map +1 -0
  313. package/dist/types/handle.d.ts +3 -0
  314. package/dist/types/handle.d.ts.map +1 -0
  315. package/dist/types/handle.js +22 -0
  316. package/dist/types/handle.js.map +1 -0
  317. package/dist/types/invite-code.d.ts +4 -0
  318. package/dist/types/invite-code.d.ts.map +1 -0
  319. package/dist/types/invite-code.js +6 -0
  320. package/dist/types/invite-code.js.map +1 -0
  321. package/dist/types/password.d.ts +4 -0
  322. package/dist/types/password.d.ts.map +1 -0
  323. package/dist/types/password.js +7 -0
  324. package/dist/types/password.js.map +1 -0
  325. package/package.json +10 -7
  326. package/src/access-token/access-token-mode.ts +4 -0
  327. package/src/account/account-manager.ts +105 -75
  328. package/src/account/account-store.ts +118 -114
  329. package/src/account/sign-in-data.ts +10 -10
  330. package/src/account/sign-up-input.ts +13 -4
  331. package/src/client/client-manager.ts +34 -2
  332. package/src/client/client-store.ts +1 -1
  333. package/src/constants.ts +6 -1
  334. package/src/customization/branding.ts +12 -0
  335. package/src/customization/build-customization-css.ts +30 -0
  336. package/src/customization/build-customization-data.ts +22 -0
  337. package/src/customization/colors.ts +30 -0
  338. package/src/customization/customization.ts +25 -0
  339. package/src/customization/links.ts +10 -0
  340. package/src/device/device-id.ts +5 -0
  341. package/src/device/device-manager.ts +76 -66
  342. package/src/device/device-store.ts +2 -0
  343. package/src/errors/access-denied-error.ts +24 -17
  344. package/src/{output/build-error-payload.ts → errors/error-parser.ts} +1 -1
  345. package/src/errors/invalid-grant-error.ts +5 -0
  346. package/src/errors/login-required-error.ts +10 -0
  347. package/src/index.ts +1 -0
  348. package/src/lib/html/build-document.ts +6 -4
  349. package/src/{output/backend-data.ts → lib/html/hydration-data.ts} +7 -5
  350. package/src/lib/html/tags.ts +2 -2
  351. package/src/lib/http/accept.ts +3 -3
  352. package/src/lib/http/context.ts +41 -10
  353. package/src/lib/http/headers.ts +15 -0
  354. package/src/lib/http/index.ts +1 -0
  355. package/src/lib/http/middleware.ts +8 -23
  356. package/src/lib/http/request.ts +40 -75
  357. package/src/lib/http/response.ts +39 -15
  358. package/src/lib/http/route.ts +8 -5
  359. package/src/lib/http/router.ts +40 -46
  360. package/src/lib/http/security-headers.ts +1 -1
  361. package/src/lib/http/types.ts +1 -6
  362. package/src/{output → lib}/send-web-page.ts +10 -9
  363. package/src/lib/util/color.ts +132 -0
  364. package/src/lib/util/crypto.ts +9 -4
  365. package/src/lib/util/function.ts +14 -0
  366. package/src/lib/util/locale.ts +18 -0
  367. package/src/lib/util/time.ts +3 -4
  368. package/src/lib/util/type.ts +24 -0
  369. package/src/lib/util/ui8.ts +14 -0
  370. package/src/lib/util/zod-error.ts +14 -0
  371. package/src/oauth-errors.ts +22 -22
  372. package/src/oauth-hooks.ts +11 -24
  373. package/src/oauth-middleware.ts +53 -0
  374. package/src/oauth-provider.ts +290 -1061
  375. package/src/oauth-verifier.ts +9 -55
  376. package/src/request/request-data.ts +5 -4
  377. package/src/request/request-manager.ts +11 -11
  378. package/src/request/request-store.ts +7 -0
  379. package/src/result/authorization-redirect-parameters.ts +24 -0
  380. package/src/result/authorization-result-authorize-page.ts +14 -0
  381. package/src/result/authorization-result-redirect.ts +8 -0
  382. package/src/router/assets/assets-manifest.ts +108 -0
  383. package/src/router/assets/assets.ts +54 -0
  384. package/src/router/assets/csrf.ts +63 -0
  385. package/src/router/assets/send-account-page.ts +43 -0
  386. package/src/router/assets/send-authorization-page.ts +62 -0
  387. package/src/router/assets/send-error-page.ts +42 -0
  388. package/src/router/create-account-page-middleware.ts +69 -0
  389. package/src/router/create-api-middleware.ts +814 -0
  390. package/src/router/create-authorization-page-middleware.ts +173 -0
  391. package/src/router/create-oauth-middleware.ts +247 -0
  392. package/src/router/error-handler.ts +6 -0
  393. package/src/router/middleware-options.ts +9 -0
  394. package/src/router/send-redirect.ts +142 -0
  395. package/src/signer/api-token-payload.ts +18 -0
  396. package/src/signer/signed-token-payload.ts +18 -28
  397. package/src/signer/signer.ts +49 -34
  398. package/src/token/token-data.ts +1 -1
  399. package/src/token/token-manager.ts +190 -239
  400. package/src/token/token-store.ts +6 -4
  401. package/src/token/verify-token-claims.ts +4 -4
  402. package/src/types/email-otp.ts +3 -0
  403. package/src/types/email.ts +26 -0
  404. package/src/types/handle.ts +18 -0
  405. package/src/types/invite-code.ts +4 -0
  406. package/src/types/password.ts +4 -0
  407. package/tsconfig.build.tsbuildinfo +1 -0
  408. package/tsconfig.json +1 -1
  409. package/dist/access-token/access-token-type.d.ts +0 -6
  410. package/dist/access-token/access-token-type.d.ts.map +0 -1
  411. package/dist/access-token/access-token-type.js +0 -10
  412. package/dist/access-token/access-token-type.js.map +0 -1
  413. package/dist/account/account.d.ts +0 -2
  414. package/dist/account/account.d.ts.map +0 -1
  415. package/dist/account/account.js.map +0 -1
  416. package/dist/assets/assets-middleware.d.ts +0 -5
  417. package/dist/assets/assets-middleware.d.ts.map +0 -1
  418. package/dist/assets/assets-middleware.js +0 -41
  419. package/dist/assets/assets-middleware.js.map +0 -1
  420. package/dist/lib/locale.d.ts +0 -15
  421. package/dist/lib/locale.d.ts.map +0 -1
  422. package/dist/lib/locale.js +0 -17
  423. package/dist/lib/locale.js.map +0 -1
  424. package/dist/output/backend-data.d.ts +0 -4
  425. package/dist/output/backend-data.d.ts.map +0 -1
  426. package/dist/output/backend-data.js.map +0 -1
  427. package/dist/output/build-authorize-data.d.ts +0 -29
  428. package/dist/output/build-authorize-data.d.ts.map +0 -1
  429. package/dist/output/build-authorize-data.js +0 -21
  430. package/dist/output/build-authorize-data.js.map +0 -1
  431. package/dist/output/build-customization-data.d.ts +0 -234
  432. package/dist/output/build-customization-data.d.ts.map +0 -1
  433. package/dist/output/build-customization-data.js +0 -174
  434. package/dist/output/build-customization-data.js.map +0 -1
  435. package/dist/output/build-error-data.d.ts +0 -3
  436. package/dist/output/build-error-data.d.ts.map +0 -1
  437. package/dist/output/build-error-data.js +0 -10
  438. package/dist/output/build-error-data.js.map +0 -1
  439. package/dist/output/build-error-payload.d.ts.map +0 -1
  440. package/dist/output/build-error-payload.js.map +0 -1
  441. package/dist/output/output-manager.d.ts +0 -28
  442. package/dist/output/output-manager.d.ts.map +0 -1
  443. package/dist/output/output-manager.js +0 -134
  444. package/dist/output/output-manager.js.map +0 -1
  445. package/dist/output/send-authorize-redirect.d.ts +0 -25
  446. package/dist/output/send-authorize-redirect.d.ts.map +0 -1
  447. package/dist/output/send-authorize-redirect.js.map +0 -1
  448. package/dist/output/send-web-page.d.ts +0 -8
  449. package/dist/output/send-web-page.d.ts.map +0 -1
  450. package/dist/output/send-web-page.js.map +0 -1
  451. package/dist/token/token-claims.d.ts.map +0 -1
  452. package/dist/token/token-claims.js +0 -27
  453. package/dist/token/token-claims.js.map +0 -1
  454. package/src/access-token/access-token-type.ts +0 -5
  455. package/src/account/account.ts +0 -1
  456. package/src/assets/assets-middleware.ts +0 -44
  457. package/src/lib/locale.ts +0 -21
  458. package/src/output/build-authorize-data.ts +0 -53
  459. package/src/output/build-customization-data.ts +0 -217
  460. package/src/output/build-error-data.ts +0 -8
  461. package/src/output/output-manager.ts +0 -188
  462. package/src/output/send-authorize-redirect.ts +0 -137
  463. package/src/token/token-claims.ts +0 -30
  464. package/tsconfig.backend.tsbuildinfo +0 -1
  465. /package/{tsconfig.backend.json → tsconfig.build.json} +0 -0
@@ -0,0 +1,814 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+ import createHttpError from 'http-errors'
3
+ import { z } from 'zod'
4
+ import { signedJwtSchema } from '@atproto/jwk'
5
+ import {
6
+ API_ENDPOINT_PREFIX,
7
+ ActiveAccountSession,
8
+ ActiveDeviceSession,
9
+ ActiveOAuthSession,
10
+ ApiEndpoints,
11
+ ISODateString,
12
+ } from '@atproto/oauth-provider-api'
13
+ import {
14
+ OAuthAuthorizationRequestParameters,
15
+ OAuthRedirectUri,
16
+ OAuthResponseMode,
17
+ oauthRedirectUriSchema,
18
+ oauthResponseModeSchema,
19
+ } from '@atproto/oauth-types'
20
+ import { signInDataSchema } from '../account/sign-in-data.js'
21
+ import { signUpInputSchema } from '../account/sign-up-input.js'
22
+ import { DeviceId, deviceIdSchema } from '../device/device-id.js'
23
+ import { AccessDeniedError } from '../errors/access-denied-error.js'
24
+ import { buildErrorPayload, buildErrorStatus } from '../errors/error-parser.js'
25
+ import { InvalidRequestError } from '../errors/invalid-request-error.js'
26
+ import { WWWAuthenticateError } from '../errors/www-authenticate-error.js'
27
+ import {
28
+ Middleware,
29
+ RequestMetadata,
30
+ Router,
31
+ RouterCtx,
32
+ SubCtx,
33
+ jsonHandler,
34
+ parseHttpRequest,
35
+ subCtx,
36
+ validateFetchMode,
37
+ validateFetchSite,
38
+ validateOrigin,
39
+ validateReferrer,
40
+ } from '../lib/http/index.js'
41
+ import { RouteCtx, createRoute } from '../lib/http/route.js'
42
+ import { asArray } from '../lib/util/cast.js'
43
+ import { localeSchema } from '../lib/util/locale.js'
44
+ import type { Awaitable } from '../lib/util/type.js'
45
+ import type { OAuthProvider } from '../oauth-provider.js'
46
+ import { Sub, subSchema } from '../oidc/sub.js'
47
+ import { RequestUri, requestUriSchema } from '../request/request-uri.js'
48
+ import { AuthorizationRedirectParameters } from '../result/authorization-redirect-parameters.js'
49
+ import { tokenIdSchema } from '../token/token-id.js'
50
+ import { emailOtpSchema } from '../types/email-otp.js'
51
+ import { emailSchema } from '../types/email.js'
52
+ import { handleSchema } from '../types/handle.js'
53
+ import { newPasswordSchema } from '../types/password.js'
54
+ import { validateCsrfToken } from './assets/csrf.js'
55
+ import type { MiddlewareOptions } from './middleware-options.js'
56
+ import {
57
+ ERROR_REDIRECT_KEYS,
58
+ OAuthRedirectOptions,
59
+ OAuthRedirectQueryParameter,
60
+ SUCCESS_REDIRECT_KEYS,
61
+ buildRedirectMode,
62
+ buildRedirectParams,
63
+ buildRedirectUri,
64
+ } from './send-redirect.js'
65
+
66
+ const verifyHandleSchema = z.object({ handle: handleSchema }).strict()
67
+
68
+ export function createApiMiddleware<
69
+ Ctx extends object | void = void,
70
+ Req extends IncomingMessage = IncomingMessage,
71
+ Res extends ServerResponse = ServerResponse,
72
+ >(
73
+ server: OAuthProvider,
74
+ { onError }: MiddlewareOptions<Req, Res>,
75
+ ): Middleware<Ctx, Req, Res> {
76
+ const issuerUrl = new URL(server.issuer)
77
+ const issuerOrigin = issuerUrl.origin
78
+ const router = new Router<Ctx, Req, Res>(issuerUrl)
79
+
80
+ router.use(
81
+ apiRoute({
82
+ method: 'POST',
83
+ endpoint: '/verify-handle-availability',
84
+ schema: verifyHandleSchema,
85
+ async handler() {
86
+ await server.accountManager.verifyHandleAvailability(this.input.handle)
87
+ return { available: true }
88
+ },
89
+ }),
90
+ )
91
+
92
+ router.use(
93
+ apiRoute({
94
+ method: 'POST',
95
+ endpoint: '/sign-up',
96
+ schema: signUpInputSchema,
97
+ rotateDeviceCookies: true,
98
+ async handler() {
99
+ const { deviceId, deviceMetadata, input, requestUri } = this
100
+
101
+ const account = await server.accountManager.createAccount(
102
+ deviceId,
103
+ deviceMetadata,
104
+ input,
105
+ )
106
+
107
+ // Remember when not in the context of a request by default
108
+ const remember = requestUri == null
109
+
110
+ // Only "remember" the newly created account if it was not created during an
111
+ // OAuth flow.
112
+ if (remember) {
113
+ await server.accountManager.upsertDeviceAccount(deviceId, account.sub)
114
+ }
115
+
116
+ const ephemeralToken = remember
117
+ ? undefined
118
+ : await server.signer.createEphemeralToken({
119
+ sub: account.sub,
120
+ deviceId,
121
+ requestUri: this.requestUri,
122
+ })
123
+
124
+ return { account, ephemeralToken }
125
+ },
126
+ }),
127
+ )
128
+
129
+ router.use(
130
+ apiRoute({
131
+ method: 'POST',
132
+ endpoint: '/sign-in',
133
+ schema: signInDataSchema.extend({ remember: z.boolean().optional() }),
134
+ rotateDeviceCookies: true,
135
+ async handler() {
136
+ const { deviceId, deviceMetadata, requestUri } = this
137
+
138
+ // Remember when not in the context of a request by default
139
+ const { remember = requestUri == null, ...input } = this.input
140
+
141
+ const account = await server.accountManager.authenticateAccount(
142
+ deviceId,
143
+ deviceMetadata,
144
+ input,
145
+ )
146
+
147
+ if (remember) {
148
+ await server.accountManager.upsertDeviceAccount(deviceId, account.sub)
149
+ } else {
150
+ // In case the user was already signed in, and signed in again, this
151
+ // time without "remember me", let's sign them off of the device.
152
+ await server.accountManager.removeDeviceAccount(deviceId, account.sub)
153
+ }
154
+
155
+ const ephemeralToken = remember
156
+ ? undefined
157
+ : await server.signer.createEphemeralToken({
158
+ sub: account.sub,
159
+ deviceId,
160
+ requestUri,
161
+ })
162
+
163
+ if (requestUri) {
164
+ // Check if a consent is required for the client, but only if this
165
+ // call is made within the context of an oauth request.
166
+
167
+ const { clientId, parameters } = await server.requestManager.get(
168
+ requestUri,
169
+ deviceId,
170
+ )
171
+
172
+ const { authorizedClients } = await server.accountManager.getAccount(
173
+ account.sub,
174
+ )
175
+
176
+ return {
177
+ account,
178
+ ephemeralToken,
179
+ consentRequired: server.checkConsentRequired(
180
+ parameters,
181
+ authorizedClients.get(clientId),
182
+ ),
183
+ }
184
+ }
185
+
186
+ return { account, ephemeralToken }
187
+ },
188
+ }),
189
+ )
190
+
191
+ router.use(
192
+ apiRoute({
193
+ method: 'POST',
194
+ endpoint: '/sign-out',
195
+ schema: z
196
+ .object({
197
+ sub: z.union([subSchema, z.array(subSchema)]),
198
+ })
199
+ .strict(),
200
+ rotateDeviceCookies: true,
201
+ async handler() {
202
+ const uniqueSubs = new Set(asArray(this.input.sub))
203
+
204
+ for (const sub of uniqueSubs) {
205
+ await server.accountManager.removeDeviceAccount(this.deviceId, sub)
206
+ }
207
+
208
+ return { success: true as const }
209
+ },
210
+ }),
211
+ )
212
+
213
+ router.use(
214
+ apiRoute({
215
+ method: 'POST',
216
+ endpoint: '/reset-password-request',
217
+ schema: z
218
+ .object({
219
+ locale: localeSchema,
220
+ email: emailSchema,
221
+ })
222
+ .strict(),
223
+ async handler() {
224
+ await server.accountManager.resetPasswordRequest(this.input)
225
+ return { success: true }
226
+ },
227
+ }),
228
+ )
229
+
230
+ router.use(
231
+ apiRoute({
232
+ method: 'POST',
233
+ endpoint: '/reset-password-confirm',
234
+ schema: z
235
+ .object({
236
+ token: emailOtpSchema,
237
+ password: newPasswordSchema,
238
+ })
239
+ .strict(),
240
+ async handler() {
241
+ await server.accountManager.resetPasswordConfirm(this.input)
242
+ return { success: true }
243
+ },
244
+ }),
245
+ )
246
+
247
+ router.use(
248
+ apiRoute({
249
+ method: 'GET',
250
+ endpoint: '/device-sessions',
251
+ schema: undefined,
252
+ async handler() {
253
+ const deviceAccounts = await server.accountManager.listDeviceAccounts(
254
+ this.deviceId,
255
+ )
256
+
257
+ return deviceAccounts.map(
258
+ (deviceAccount): ActiveDeviceSession => ({
259
+ account: deviceAccount.account,
260
+ loginRequired: server.checkLoginRequired(deviceAccount),
261
+ }),
262
+ )
263
+ },
264
+ }),
265
+ )
266
+
267
+ router.use(
268
+ apiRoute({
269
+ method: 'GET',
270
+ endpoint: '/oauth-sessions',
271
+ schema: z.object({ sub: subSchema }).strict(),
272
+ async handler(req, res) {
273
+ const { account } = await authenticate.call(this, req, res)
274
+
275
+ const tokenInfos = await server.tokenManager.listAccountTokens(
276
+ account.sub,
277
+ )
278
+
279
+ const clientIds = tokenInfos.map((tokenInfo) => tokenInfo.data.clientId)
280
+
281
+ const clients = await server.clientManager.loadClients(clientIds, {
282
+ onError: (err, clientId) => {
283
+ onError?.(req, res, err, `Failed to load client ${clientId}`)
284
+ return undefined // metadata won't be available in the UI
285
+ },
286
+ })
287
+
288
+ // @TODO: We should ideally filter sessions that are expired (or even
289
+ // expose the expiration date). This requires a change to the way
290
+ // TokenInfo are stored (see TokenManager#isTokenExpired and
291
+ // TokenManager#isTokenInactive).
292
+ return tokenInfos.map(({ id, data }): ActiveOAuthSession => {
293
+ return {
294
+ tokenId: id,
295
+
296
+ createdAt: data.createdAt.toISOString() as ISODateString,
297
+ updatedAt: data.updatedAt.toISOString() as ISODateString,
298
+
299
+ clientId: data.clientId,
300
+ clientMetadata: clients.get(data.clientId)?.metadata,
301
+
302
+ scope: data.parameters.scope,
303
+ }
304
+ })
305
+ },
306
+ }),
307
+ )
308
+
309
+ router.use(
310
+ apiRoute({
311
+ method: 'GET',
312
+ endpoint: '/account-sessions',
313
+ schema: z.object({ sub: subSchema }).strict(),
314
+ async handler(req, res) {
315
+ const { account } = await authenticate.call(this, req, res)
316
+
317
+ const deviceAccounts = await server.accountManager.listAccountDevices(
318
+ account.sub,
319
+ )
320
+
321
+ return deviceAccounts.map(
322
+ (accountSession): ActiveAccountSession => ({
323
+ deviceId: accountSession.deviceId,
324
+ deviceMetadata: {
325
+ ipAddress: accountSession.deviceData.ipAddress,
326
+ userAgent: accountSession.deviceData.userAgent,
327
+ lastSeenAt:
328
+ accountSession.deviceData.lastSeenAt.toISOString() as ISODateString,
329
+ },
330
+
331
+ isCurrentDevice: accountSession.deviceId === this.deviceId,
332
+ }),
333
+ )
334
+ },
335
+ }),
336
+ )
337
+
338
+ router.use(
339
+ apiRoute({
340
+ method: 'POST',
341
+ endpoint: '/revoke-account-session',
342
+ schema: z.object({ sub: subSchema, deviceId: deviceIdSchema }).strict(),
343
+ async handler() {
344
+ // @NOTE This route is not authenticated. If a user is able to steal
345
+ // another user's session cookie, we allow them to revoke the device
346
+ // session.
347
+
348
+ await server.accountManager.removeDeviceAccount(
349
+ this.input.deviceId,
350
+ this.input.sub,
351
+ )
352
+
353
+ return { success: true }
354
+ },
355
+ }),
356
+ )
357
+
358
+ router.use(
359
+ apiRoute({
360
+ method: 'POST',
361
+ endpoint: '/revoke-oauth-session',
362
+ schema: z.object({ sub: subSchema, tokenId: tokenIdSchema }).strict(),
363
+ async handler(req, res) {
364
+ const { account } = await authenticate.call(this, req, res)
365
+
366
+ const tokenInfo = await server.tokenManager.getTokenInfo(
367
+ this.input.tokenId,
368
+ )
369
+
370
+ if (tokenInfo.account.sub !== account.sub) {
371
+ // report this as though the token was not found
372
+ throw new InvalidRequestError(`Invalid token`)
373
+ }
374
+
375
+ await server.tokenManager.deleteToken(tokenInfo.id)
376
+
377
+ return { success: true }
378
+ },
379
+ }),
380
+ )
381
+
382
+ router.use(
383
+ apiRoute({
384
+ method: 'POST',
385
+ endpoint: '/accept',
386
+ schema: z.object({ sub: z.union([subSchema, signedJwtSchema]) }).strict(),
387
+ async handler(req, res) {
388
+ if (!this.requestUri) {
389
+ throw new InvalidRequestError(
390
+ 'This endpoint can only be used in the context of an OAuth request',
391
+ )
392
+ }
393
+
394
+ // Any AccessDeniedError caught in this block will result in a redirect
395
+ // to the client's redirect_uri with an error.
396
+ try {
397
+ const { clientId, parameters } = await server.requestManager.get(
398
+ this.requestUri,
399
+ this.deviceId,
400
+ )
401
+
402
+ // Any error thrown in this block will be transformed into an
403
+ // AccessDeniedError.
404
+ try {
405
+ const { account, authorizedClients } = await authenticate.call(
406
+ this,
407
+ req,
408
+ res,
409
+ )
410
+
411
+ const client = await server.clientManager.getClient(clientId)
412
+
413
+ const code = await server.requestManager.setAuthorized(
414
+ this.requestUri,
415
+ client,
416
+ account,
417
+ this.deviceId,
418
+ this.deviceMetadata,
419
+ )
420
+
421
+ const clientData = authorizedClients.get(clientId)
422
+ if (server.checkConsentRequired(parameters, clientData)) {
423
+ const scopes = new Set(clientData?.authorizedScopes)
424
+
425
+ // Add the newly accepted scopes to the authorized scopes
426
+
427
+ // @NOTE `oauthScopeSchema` ensures that `scope` contains no
428
+ // leading/trailing/duplicate spaces.
429
+ for (const s of parameters.scope?.split(' ') ?? []) scopes.add(s)
430
+
431
+ await server.accountManager.setAuthorizedClient(account, client, {
432
+ ...clientData,
433
+ authorizedScopes: [...scopes],
434
+ })
435
+ }
436
+
437
+ const url = buildRedirectUrl(server.issuer, parameters, { code })
438
+
439
+ return { url }
440
+ } catch (err) {
441
+ // Since we have access to the parameters, we can re-throw an
442
+ // AccessDeniedError with the redirect_uri parameter.
443
+ throw AccessDeniedError.from(parameters, err, 'server_error')
444
+ }
445
+ } catch (err) {
446
+ // If any error happened (unauthenticated, invalid request, etc.),
447
+ // lets make sure the request can no longer be used.
448
+ try {
449
+ await server.requestManager.delete(this.requestUri)
450
+ } catch (err) {
451
+ onError?.(req, res, err, 'Failed to delete request')
452
+ }
453
+
454
+ if (err instanceof AccessDeniedError && err.parameters.redirect_uri) {
455
+ // Prefer logging the cause
456
+ onError?.(req, res, err.cause ?? err, 'Authorization failed')
457
+
458
+ const url = buildRedirectUrl(
459
+ server.issuer,
460
+ err.parameters,
461
+ err.toJSON(),
462
+ )
463
+
464
+ return { url }
465
+ }
466
+
467
+ throw err
468
+ }
469
+ },
470
+ }),
471
+ )
472
+
473
+ router.use(
474
+ apiRoute({
475
+ method: 'POST',
476
+ endpoint: '/reject',
477
+ schema: z.object({}).strict(),
478
+ rotateDeviceCookies: true,
479
+ async handler(req, res) {
480
+ const { requestUri } = this
481
+ if (!requestUri) {
482
+ throw new InvalidRequestError(
483
+ 'This endpoint can only be used in the context of an OAuth request',
484
+ )
485
+ }
486
+
487
+ // Once this endpoint is called, the request will definitely be
488
+ // rejected.
489
+ try {
490
+ // No need to authenticate the user here as they are not authorizing a
491
+ // particular account (CSRF protection is enough).
492
+
493
+ // @NOTE that the client could *technically* trigger this endpoint while
494
+ // the user is on the authorize page by forging the request (because the
495
+ // client knows the RequestURI from PAR and has all the info needed to
496
+ // forge the request, including CSRF). This cannot be used as DoS attack
497
+ // as the request ID is not guessable and would only result in a bad UX
498
+ // for misbehaving clients, only for the users of those clients.
499
+
500
+ const { parameters } = await server.requestManager.get(
501
+ requestUri,
502
+ this.deviceId,
503
+ )
504
+
505
+ const url = buildRedirectUrl(server.issuer, parameters, {
506
+ error: 'access_denied',
507
+ error_description: 'The user rejected the request',
508
+ })
509
+
510
+ return { url }
511
+ } catch (err) {
512
+ if (err instanceof AccessDeniedError && err.parameters.redirect_uri) {
513
+ // Prefer logging the cause
514
+ onError?.(req, res, err.cause ?? err, 'Authorization failed')
515
+
516
+ const url = buildRedirectUrl(
517
+ server.issuer,
518
+ err.parameters,
519
+ err.toJSON(),
520
+ )
521
+
522
+ return { url }
523
+ }
524
+
525
+ throw err
526
+ } finally {
527
+ await server.requestManager.delete(requestUri).catch((err) => {
528
+ onError?.(req, res, err, 'Failed to delete request')
529
+ })
530
+ }
531
+ },
532
+ }),
533
+ )
534
+
535
+ return router.buildMiddleware()
536
+
537
+ async function authenticate(
538
+ this: ApiContext<void, { sub: Sub }>,
539
+ req: Req,
540
+ res: Res,
541
+ ) {
542
+ const authorization = req.headers.authorization?.split(' ')
543
+ if (authorization?.[0].toLowerCase() === 'bearer') {
544
+ try {
545
+ // If there is an authorization header, verify that the ephemeral token it
546
+ // contains is a jwt bound to the right [sub, device, request].
547
+ const ephemeralToken = signedJwtSchema.parse(authorization[1])
548
+ const { payload } =
549
+ await server.signer.verifyEphemeralToken(ephemeralToken)
550
+
551
+ if (
552
+ payload.sub === this.input.sub &&
553
+ payload.deviceId === this.deviceId &&
554
+ payload.requestUri === this.requestUri
555
+ ) {
556
+ return await server.accountManager.getAccount(payload.sub)
557
+ }
558
+ } catch (err) {
559
+ onError?.(req, res, err, 'Failed to authenticate ephemeral token')
560
+ // Fall back to session based authentication
561
+ }
562
+ }
563
+
564
+ try {
565
+ // Ensures the "sub" has an active session on the device
566
+ const deviceAccount = await server.accountManager.getDeviceAccount(
567
+ this.deviceId,
568
+ this.input.sub,
569
+ )
570
+
571
+ // The session exists but was created too long ago
572
+ if (server.checkLoginRequired(deviceAccount)) {
573
+ throw new InvalidRequestError('Login required')
574
+ }
575
+
576
+ return deviceAccount
577
+ } catch (err) {
578
+ throw new WWWAuthenticateError(
579
+ 'unauthorized',
580
+ `User ${this.input.sub} not authenticated on this device`,
581
+ { Bearer: {} },
582
+ err,
583
+ )
584
+ }
585
+ }
586
+
587
+ type ApiContext<T extends object | void, I = void> = SubCtx<
588
+ T,
589
+ {
590
+ deviceId: DeviceId
591
+ deviceMetadata: RequestMetadata
592
+
593
+ /**
594
+ * The parsed input data (json payload if "POST", query params if "GET").
595
+ */
596
+ input: I
597
+
598
+ /**
599
+ * When defined, the request originated from the authorize page.
600
+ */
601
+ requestUri?: RequestUri
602
+ }
603
+ >
604
+
605
+ type InferValidation<S extends void | z.ZodTypeAny> = S extends z.ZodTypeAny
606
+ ? z.infer<S>
607
+ : void
608
+
609
+ /**
610
+ * The main purpose of this function is to ensure that the endpoint
611
+ * implementation matches its type definition from {@link ApiEndpoints}.
612
+ * @private
613
+ */
614
+ function apiRoute<
615
+ C extends RouterCtx<Ctx>,
616
+ M extends 'GET' | 'POST',
617
+ E extends `/${string}` &
618
+ // Extract all the endpoint path that match the method (allows for
619
+ // auto-complete & better error reporting)
620
+ {
621
+ [E in keyof ApiEndpoints]: ApiEndpoints[E] extends { method: M }
622
+ ? E
623
+ : never
624
+ }[keyof ApiEndpoints],
625
+ S extends // A schema that validates the POST input or GET params
626
+ ApiEndpoints[E] extends { method: 'POST'; input: infer I }
627
+ ? z.ZodType<I>
628
+ : ApiEndpoints[E] extends { method: 'GET'; params: infer P }
629
+ ? z.ZodType<P>
630
+ : void,
631
+ >(options: {
632
+ method: M
633
+ endpoint: E
634
+ schema: S
635
+ rotateDeviceCookies?: boolean
636
+ handler: (
637
+ this: ApiContext<RouteCtx<C>, InferValidation<S>>,
638
+ req: Req,
639
+ res: Res,
640
+ ) => Awaitable<ApiEndpoints[E]['output']>
641
+ }): Middleware<C, Req, Res> {
642
+ return createRoute(
643
+ options.method,
644
+ `${API_ENDPOINT_PREFIX}${options.endpoint}`,
645
+ apiMiddleware(options),
646
+ )
647
+ }
648
+
649
+ function apiMiddleware<C extends RouterCtx, S extends void | z.ZodTypeAny>({
650
+ method,
651
+ schema,
652
+ rotateDeviceCookies,
653
+ handler,
654
+ }: {
655
+ method: 'GET' | 'POST'
656
+ schema: S
657
+ rotateDeviceCookies?: boolean
658
+ handler: (
659
+ this: ApiContext<C, InferValidation<S>>,
660
+ req: Req,
661
+ res: Res,
662
+ ) => unknown
663
+ }): Middleware<C, Req, Res> {
664
+ const parseInput: (this: C, req: Req) => Promise<InferValidation<S>> =
665
+ schema == null // No schema means endpoint doesn't accept any input
666
+ ? async function (req) {
667
+ req.resume() // Flush body
668
+ return undefined
669
+ }
670
+ : method === 'POST'
671
+ ? async function (req) {
672
+ const body = await parseHttpRequest(req, ['json'])
673
+ return schema.parseAsync(body, { path: ['body'] })
674
+ }
675
+ : async function (req) {
676
+ // @NOTE This should not be necessary with GET requests
677
+ req.resume().once('error', (_err) => {
678
+ // Ignore errors when flushing the request body
679
+ // (e.g. client closed connection)
680
+ })
681
+
682
+ const query = Object.fromEntries(this.url.searchParams)
683
+ return schema.parseAsync(query, { path: ['query'] })
684
+ }
685
+
686
+ return jsonHandler<C, Req, Res>(async function (req, res) {
687
+ try {
688
+ // Prevent caching of API routes
689
+ res.setHeader('Cache-Control', 'no-store')
690
+ res.setHeader('Pragma', 'no-cache')
691
+
692
+ // Prevent CORS requests
693
+ validateFetchMode(req, ['same-origin'])
694
+ validateFetchSite(req, ['same-origin'])
695
+ validateOrigin(req, issuerOrigin)
696
+ const referrer = validateReferrer(req, { origin: issuerOrigin })
697
+
698
+ // Ensure we are one the right page
699
+ if (
700
+ // trailing slashes are not allowed
701
+ referrer.pathname !== '/oauth/authorize' &&
702
+ referrer.pathname !== '/account' &&
703
+ !referrer.pathname.startsWith(`/account/`)
704
+ ) {
705
+ throw createHttpError(400, `Invalid referrer ${referrer}`)
706
+ }
707
+
708
+ // Check if the request originated from the authorize page
709
+ const requestUri =
710
+ referrer.pathname === '/oauth/authorize'
711
+ ? await requestUriSchema.parseAsync(
712
+ referrer.searchParams.get('request_uri'),
713
+ )
714
+ : undefined
715
+
716
+ // Validate CSRF token
717
+ await validateCsrfToken(req, res)
718
+
719
+ // Parse and validate the input data
720
+ const input = await parseInput.call(this, req)
721
+
722
+ // Load session data, rotating the session cookie if needed
723
+ const { deviceId, deviceMetadata } = await server.deviceManager.load(
724
+ req,
725
+ res,
726
+ rotateDeviceCookies,
727
+ )
728
+
729
+ const context = subCtx(this, {
730
+ input,
731
+ requestUri,
732
+ deviceId,
733
+ deviceMetadata,
734
+ })
735
+
736
+ // Generate the API response
737
+ const payload = await handler.call(context, req, res)
738
+
739
+ return { payload, status: 200 }
740
+ } catch (err) {
741
+ onError?.(req, res, err, 'Failed to handle API request')
742
+
743
+ // @TODO Rework the API error responses (relying on codes)
744
+ const payload = buildErrorPayload(err)
745
+ const status = buildErrorStatus(err)
746
+
747
+ return { payload, status }
748
+ }
749
+ })
750
+ }
751
+ }
752
+
753
+ function buildRedirectUrl(
754
+ iss: string,
755
+ parameters: OAuthAuthorizationRequestParameters,
756
+ redirect: AuthorizationRedirectParameters,
757
+ ): string {
758
+ const url = new URL('/oauth/authorize/redirect', iss)
759
+
760
+ url.searchParams.set('redirect_mode', buildRedirectMode(parameters))
761
+ url.searchParams.set('redirect_uri', buildRedirectUri(parameters))
762
+
763
+ for (const [key, value] of buildRedirectParams(iss, parameters, redirect)) {
764
+ url.searchParams.set(key, value)
765
+ }
766
+
767
+ return url.href
768
+ }
769
+
770
+ export function parseRedirectUrl(url: URL): OAuthRedirectOptions {
771
+ if (url.pathname !== '/oauth/authorize/redirect') {
772
+ throw new InvalidRequestError(
773
+ `Invalid redirect URL: ${url.pathname} is not a valid path`,
774
+ )
775
+ }
776
+
777
+ const params: [OAuthRedirectQueryParameter, string][] = []
778
+
779
+ const state = url.searchParams.get('state')
780
+ if (state) params.push(['state', state])
781
+
782
+ const iss = url.searchParams.get('iss')
783
+ if (iss) params.push(['iss', iss])
784
+
785
+ if (url.searchParams.has('code')) {
786
+ for (const key of SUCCESS_REDIRECT_KEYS) {
787
+ const value = url.searchParams.get(key)
788
+ if (value != null) params.push([key, value])
789
+ }
790
+ } else if (url.searchParams.has('error')) {
791
+ for (const key of ERROR_REDIRECT_KEYS) {
792
+ const value = url.searchParams.get(key)
793
+ if (value != null) params.push([key, value])
794
+ }
795
+ } else {
796
+ throw new InvalidRequestError(
797
+ 'Invalid redirect URL: neither code nor error found',
798
+ )
799
+ }
800
+
801
+ try {
802
+ const mode: OAuthResponseMode = oauthResponseModeSchema.parse(
803
+ url.searchParams.get('redirect_mode'),
804
+ )
805
+
806
+ const redirectUri: OAuthRedirectUri = oauthRedirectUriSchema.parse(
807
+ url.searchParams.get('redirect_uri'),
808
+ )
809
+
810
+ return { mode, redirectUri, params }
811
+ } catch (err) {
812
+ throw InvalidRequestError.from(err, 'Invalid redirect URL')
813
+ }
814
+ }