@atproto/oauth-provider 0.1.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 (631) hide show
  1. package/.postcssrc.yml +3 -0
  2. package/CHANGELOG.md +19 -0
  3. package/LICENSE.txt +7 -0
  4. package/dist/access-token/access-token-type.d.ts +6 -0
  5. package/dist/access-token/access-token-type.d.ts.map +1 -0
  6. package/dist/access-token/access-token-type.js +10 -0
  7. package/dist/access-token/access-token-type.js.map +1 -0
  8. package/dist/account/account-manager.d.ts +14 -0
  9. package/dist/account/account-manager.d.ts.map +1 -0
  10. package/dist/account/account-manager.js +39 -0
  11. package/dist/account/account-manager.js.map +1 -0
  12. package/dist/account/account-store.d.ts +39 -0
  13. package/dist/account/account-store.d.ts.map +1 -0
  14. package/dist/account/account-store.js +19 -0
  15. package/dist/account/account-store.js.map +1 -0
  16. package/dist/account/account.d.ts +8 -0
  17. package/dist/account/account.d.ts.map +1 -0
  18. package/dist/account/account.js +3 -0
  19. package/dist/account/account.js.map +1 -0
  20. package/dist/assets/app/bundle-manifest.json +22 -0
  21. package/dist/assets/app/main.css +3 -0
  22. package/dist/assets/app/main.js +20 -0
  23. package/dist/assets/app/main.js.map +1 -0
  24. package/dist/assets/asset.d.ts +9 -0
  25. package/dist/assets/asset.d.ts.map +1 -0
  26. package/dist/assets/asset.js +3 -0
  27. package/dist/assets/asset.js.map +1 -0
  28. package/dist/assets/assets-middleware.d.ts +2 -0
  29. package/dist/assets/assets-middleware.d.ts.map +1 -0
  30. package/dist/assets/assets-middleware.js +30 -0
  31. package/dist/assets/assets-middleware.js.map +1 -0
  32. package/dist/assets/index.d.ts +4 -0
  33. package/dist/assets/index.d.ts.map +1 -0
  34. package/dist/assets/index.js +65 -0
  35. package/dist/assets/index.js.map +1 -0
  36. package/dist/client/client-auth.d.ts +13 -0
  37. package/dist/client/client-auth.d.ts.map +1 -0
  38. package/dist/client/client-auth.js +35 -0
  39. package/dist/client/client-auth.js.map +1 -0
  40. package/dist/client/client-data.d.ts +8 -0
  41. package/dist/client/client-data.d.ts.map +1 -0
  42. package/dist/client/client-data.js +3 -0
  43. package/dist/client/client-data.js.map +1 -0
  44. package/dist/client/client-id.d.ts +4 -0
  45. package/dist/client/client-id.d.ts.map +1 -0
  46. package/dist/client/client-id.js +6 -0
  47. package/dist/client/client-id.js.map +1 -0
  48. package/dist/client/client-info.d.ts +13 -0
  49. package/dist/client/client-info.d.ts.map +1 -0
  50. package/dist/client/client-info.js +3 -0
  51. package/dist/client/client-info.js.map +1 -0
  52. package/dist/client/client-manager.d.ts +38 -0
  53. package/dist/client/client-manager.d.ts.map +1 -0
  54. package/dist/client/client-manager.js +534 -0
  55. package/dist/client/client-manager.js.map +1 -0
  56. package/dist/client/client-store.d.ts +13 -0
  57. package/dist/client/client-store.d.ts.map +1 -0
  58. package/dist/client/client-store.js +39 -0
  59. package/dist/client/client-store.js.map +1 -0
  60. package/dist/client/client-utils.d.ts +6 -0
  61. package/dist/client/client-utils.d.ts.map +1 -0
  62. package/dist/client/client-utils.js +40 -0
  63. package/dist/client/client-utils.js.map +1 -0
  64. package/dist/client/client.d.ts +41 -0
  65. package/dist/client/client.d.ts.map +1 -0
  66. package/dist/client/client.js +163 -0
  67. package/dist/client/client.js.map +1 -0
  68. package/dist/constants.d.ts +42 -0
  69. package/dist/constants.d.ts.map +1 -0
  70. package/dist/constants.js +53 -0
  71. package/dist/constants.js.map +1 -0
  72. package/dist/device/device-data.d.ts +20 -0
  73. package/dist/device/device-data.d.ts.map +1 -0
  74. package/dist/device/device-data.js +11 -0
  75. package/dist/device/device-data.js.map +1 -0
  76. package/dist/device/device-details.d.ts +17 -0
  77. package/dist/device/device-details.d.ts.map +1 -0
  78. package/dist/device/device-details.js +34 -0
  79. package/dist/device/device-details.js.map +1 -0
  80. package/dist/device/device-id.d.ts +6 -0
  81. package/dist/device/device-id.d.ts.map +1 -0
  82. package/dist/device/device-id.js +18 -0
  83. package/dist/device/device-id.js.map +1 -0
  84. package/dist/device/device-manager.d.ts +88 -0
  85. package/dist/device/device-manager.d.ts.map +1 -0
  86. package/dist/device/device-manager.js +206 -0
  87. package/dist/device/device-manager.js.map +1 -0
  88. package/dist/device/device-store.d.ts +15 -0
  89. package/dist/device/device-store.d.ts.map +1 -0
  90. package/dist/device/device-store.js +36 -0
  91. package/dist/device/device-store.js.map +1 -0
  92. package/dist/device/session-id.d.ts +6 -0
  93. package/dist/device/session-id.d.ts.map +1 -0
  94. package/dist/device/session-id.js +18 -0
  95. package/dist/device/session-id.js.map +1 -0
  96. package/dist/dpop/dpop-manager.d.ts +33 -0
  97. package/dist/dpop/dpop-manager.d.ts.map +1 -0
  98. package/dist/dpop/dpop-manager.js +115 -0
  99. package/dist/dpop/dpop-manager.js.map +1 -0
  100. package/dist/dpop/dpop-nonce.d.ts +13 -0
  101. package/dist/dpop/dpop-nonce.d.ts.map +1 -0
  102. package/dist/dpop/dpop-nonce.js +94 -0
  103. package/dist/dpop/dpop-nonce.js.map +1 -0
  104. package/dist/errors/access-denied-error.d.ts +8 -0
  105. package/dist/errors/access-denied-error.d.ts.map +1 -0
  106. package/dist/errors/access-denied-error.js +21 -0
  107. package/dist/errors/access-denied-error.js.map +1 -0
  108. package/dist/errors/account-selection-required-error.d.ts +6 -0
  109. package/dist/errors/account-selection-required-error.d.ts.map +1 -0
  110. package/dist/errors/account-selection-required-error.js +11 -0
  111. package/dist/errors/account-selection-required-error.js.map +1 -0
  112. package/dist/errors/consent-required-error.d.ts +6 -0
  113. package/dist/errors/consent-required-error.d.ts.map +1 -0
  114. package/dist/errors/consent-required-error.js +11 -0
  115. package/dist/errors/consent-required-error.js.map +1 -0
  116. package/dist/errors/invalid-authorization-details-error.d.ts +20 -0
  117. package/dist/errors/invalid-authorization-details-error.d.ts.map +1 -0
  118. package/dist/errors/invalid-authorization-details-error.js +26 -0
  119. package/dist/errors/invalid-authorization-details-error.js.map +1 -0
  120. package/dist/errors/invalid-client-error.d.ts +18 -0
  121. package/dist/errors/invalid-client-error.d.ts.map +1 -0
  122. package/dist/errors/invalid-client-error.js +24 -0
  123. package/dist/errors/invalid-client-error.js.map +1 -0
  124. package/dist/errors/invalid-client-id-error.d.ts +13 -0
  125. package/dist/errors/invalid-client-id-error.d.ts.map +1 -0
  126. package/dist/errors/invalid-client-id-error.js +25 -0
  127. package/dist/errors/invalid-client-id-error.js.map +1 -0
  128. package/dist/errors/invalid-client-metadata-error.d.ts +13 -0
  129. package/dist/errors/invalid-client-metadata-error.d.ts.map +1 -0
  130. package/dist/errors/invalid-client-metadata-error.js +23 -0
  131. package/dist/errors/invalid-client-metadata-error.js.map +1 -0
  132. package/dist/errors/invalid-dpop-key-binding-error.d.ts +12 -0
  133. package/dist/errors/invalid-dpop-key-binding-error.d.ts.map +1 -0
  134. package/dist/errors/invalid-dpop-key-binding-error.js +20 -0
  135. package/dist/errors/invalid-dpop-key-binding-error.js.map +1 -0
  136. package/dist/errors/invalid-dpop-proof-error.d.ts +5 -0
  137. package/dist/errors/invalid-dpop-proof-error.d.ts.map +1 -0
  138. package/dist/errors/invalid-dpop-proof-error.js +12 -0
  139. package/dist/errors/invalid-dpop-proof-error.js.map +1 -0
  140. package/dist/errors/invalid-grant-error.d.ts +14 -0
  141. package/dist/errors/invalid-grant-error.d.ts.map +1 -0
  142. package/dist/errors/invalid-grant-error.js +20 -0
  143. package/dist/errors/invalid-grant-error.js.map +1 -0
  144. package/dist/errors/invalid-parameters-error.d.ts +6 -0
  145. package/dist/errors/invalid-parameters-error.d.ts.map +1 -0
  146. package/dist/errors/invalid-parameters-error.js +11 -0
  147. package/dist/errors/invalid-parameters-error.js.map +1 -0
  148. package/dist/errors/invalid-redirect-uri-error.d.ts +11 -0
  149. package/dist/errors/invalid-redirect-uri-error.d.ts.map +1 -0
  150. package/dist/errors/invalid-redirect-uri-error.js +21 -0
  151. package/dist/errors/invalid-redirect-uri-error.js.map +1 -0
  152. package/dist/errors/invalid-request-error.d.ts +28 -0
  153. package/dist/errors/invalid-request-error.d.ts.map +1 -0
  154. package/dist/errors/invalid-request-error.js +34 -0
  155. package/dist/errors/invalid-request-error.js.map +1 -0
  156. package/dist/errors/invalid-token-error.d.ts +16 -0
  157. package/dist/errors/invalid-token-error.d.ts.map +1 -0
  158. package/dist/errors/invalid-token-error.js +45 -0
  159. package/dist/errors/invalid-token-error.js.map +1 -0
  160. package/dist/errors/login-required-error.d.ts +6 -0
  161. package/dist/errors/login-required-error.d.ts.map +1 -0
  162. package/dist/errors/login-required-error.js +11 -0
  163. package/dist/errors/login-required-error.js.map +1 -0
  164. package/dist/errors/oauth-error.d.ts +13 -0
  165. package/dist/errors/oauth-error.d.ts.map +1 -0
  166. package/dist/errors/oauth-error.js +29 -0
  167. package/dist/errors/oauth-error.js.map +1 -0
  168. package/dist/errors/unauthorized-client-error.d.ts +18 -0
  169. package/dist/errors/unauthorized-client-error.d.ts.map +1 -0
  170. package/dist/errors/unauthorized-client-error.js +24 -0
  171. package/dist/errors/unauthorized-client-error.js.map +1 -0
  172. package/dist/errors/use-dpop-nonce-error.d.ts +18 -0
  173. package/dist/errors/use-dpop-nonce-error.d.ts.map +1 -0
  174. package/dist/errors/use-dpop-nonce-error.js +27 -0
  175. package/dist/errors/use-dpop-nonce-error.js.map +1 -0
  176. package/dist/errors/www-authenticate-error.d.ts +9 -0
  177. package/dist/errors/www-authenticate-error.d.ts.map +1 -0
  178. package/dist/errors/www-authenticate-error.js +46 -0
  179. package/dist/errors/www-authenticate-error.js.map +1 -0
  180. package/dist/index.d.ts +14 -0
  181. package/dist/index.d.ts.map +1 -0
  182. package/dist/index.js +31 -0
  183. package/dist/index.js.map +1 -0
  184. package/dist/lib/html/build-document.d.ts +32 -0
  185. package/dist/lib/html/build-document.d.ts.map +1 -0
  186. package/dist/lib/html/build-document.js +61 -0
  187. package/dist/lib/html/build-document.js.map +1 -0
  188. package/dist/lib/html/escapers.d.ts +9 -0
  189. package/dist/lib/html/escapers.d.ts.map +1 -0
  190. package/dist/lib/html/escapers.js +66 -0
  191. package/dist/lib/html/escapers.js.map +1 -0
  192. package/dist/lib/html/html.d.ts +13 -0
  193. package/dist/lib/html/html.d.ts.map +1 -0
  194. package/dist/lib/html/html.js +53 -0
  195. package/dist/lib/html/html.js.map +1 -0
  196. package/dist/lib/html/index.d.ts +4 -0
  197. package/dist/lib/html/index.d.ts.map +1 -0
  198. package/dist/lib/html/index.js +21 -0
  199. package/dist/lib/html/index.js.map +1 -0
  200. package/dist/lib/html/tags.d.ts +34 -0
  201. package/dist/lib/html/tags.d.ts.map +1 -0
  202. package/dist/lib/html/tags.js +47 -0
  203. package/dist/lib/html/tags.js.map +1 -0
  204. package/dist/lib/html/util.d.ts +4 -0
  205. package/dist/lib/html/util.d.ts.map +1 -0
  206. package/dist/lib/html/util.js +20 -0
  207. package/dist/lib/html/util.js.map +1 -0
  208. package/dist/lib/http/accept.d.ts +29 -0
  209. package/dist/lib/http/accept.d.ts.map +1 -0
  210. package/dist/lib/http/accept.js +67 -0
  211. package/dist/lib/http/accept.js.map +1 -0
  212. package/dist/lib/http/context.d.ts +5 -0
  213. package/dist/lib/http/context.d.ts.map +1 -0
  214. package/dist/lib/http/context.js +10 -0
  215. package/dist/lib/http/context.js.map +1 -0
  216. package/dist/lib/http/index.d.ts +10 -0
  217. package/dist/lib/http/index.d.ts.map +1 -0
  218. package/dist/lib/http/index.js +26 -0
  219. package/dist/lib/http/index.js.map +1 -0
  220. package/dist/lib/http/method.d.ts +6 -0
  221. package/dist/lib/http/method.d.ts.map +1 -0
  222. package/dist/lib/http/method.js +19 -0
  223. package/dist/lib/http/method.js.map +1 -0
  224. package/dist/lib/http/middleware.d.ts +18 -0
  225. package/dist/lib/http/middleware.d.ts.map +1 -0
  226. package/dist/lib/http/middleware.js +118 -0
  227. package/dist/lib/http/middleware.js.map +1 -0
  228. package/dist/lib/http/parser.d.ts +33 -0
  229. package/dist/lib/http/parser.d.ts.map +1 -0
  230. package/dist/lib/http/parser.js +48 -0
  231. package/dist/lib/http/parser.js.map +1 -0
  232. package/dist/lib/http/path.d.ts +9 -0
  233. package/dist/lib/http/path.d.ts.map +1 -0
  234. package/dist/lib/http/path.js +54 -0
  235. package/dist/lib/http/path.js.map +1 -0
  236. package/dist/lib/http/request.d.ts +33 -0
  237. package/dist/lib/http/request.d.ts.map +1 -0
  238. package/dist/lib/http/request.js +86 -0
  239. package/dist/lib/http/request.js.map +1 -0
  240. package/dist/lib/http/response.d.ts +13 -0
  241. package/dist/lib/http/response.d.ts.map +1 -0
  242. package/dist/lib/http/response.js +98 -0
  243. package/dist/lib/http/response.js.map +1 -0
  244. package/dist/lib/http/route.d.ts +25 -0
  245. package/dist/lib/http/route.d.ts.map +1 -0
  246. package/dist/lib/http/route.js +39 -0
  247. package/dist/lib/http/route.js.map +1 -0
  248. package/dist/lib/http/router.d.ts +32 -0
  249. package/dist/lib/http/router.d.ts.map +1 -0
  250. package/dist/lib/http/router.js +74 -0
  251. package/dist/lib/http/router.js.map +1 -0
  252. package/dist/lib/http/stream.d.ts +13 -0
  253. package/dist/lib/http/stream.d.ts.map +1 -0
  254. package/dist/lib/http/stream.js +46 -0
  255. package/dist/lib/http/stream.js.map +1 -0
  256. package/dist/lib/http/types.d.ts +7 -0
  257. package/dist/lib/http/types.d.ts.map +1 -0
  258. package/dist/lib/http/types.js +3 -0
  259. package/dist/lib/http/types.js.map +1 -0
  260. package/dist/lib/http/url.d.ts +8 -0
  261. package/dist/lib/http/url.d.ts.map +1 -0
  262. package/dist/lib/http/url.js +22 -0
  263. package/dist/lib/http/url.js.map +1 -0
  264. package/dist/lib/redis.d.ts +5 -0
  265. package/dist/lib/redis.d.ts.map +1 -0
  266. package/dist/lib/redis.js +22 -0
  267. package/dist/lib/redis.js.map +1 -0
  268. package/dist/lib/util/authorization-header.d.ts +4 -0
  269. package/dist/lib/util/authorization-header.d.ts.map +1 -0
  270. package/dist/lib/util/authorization-header.js +23 -0
  271. package/dist/lib/util/authorization-header.js.map +1 -0
  272. package/dist/lib/util/cast.d.ts +2 -0
  273. package/dist/lib/util/cast.d.ts.map +1 -0
  274. package/dist/lib/util/cast.js +10 -0
  275. package/dist/lib/util/cast.js.map +1 -0
  276. package/dist/lib/util/crypto.d.ts +3 -0
  277. package/dist/lib/util/crypto.d.ts.map +1 -0
  278. package/dist/lib/util/crypto.js +29 -0
  279. package/dist/lib/util/crypto.js.map +1 -0
  280. package/dist/lib/util/date.d.ts +3 -0
  281. package/dist/lib/util/date.d.ts.map +1 -0
  282. package/dist/lib/util/date.js +12 -0
  283. package/dist/lib/util/date.js.map +1 -0
  284. package/dist/lib/util/hostname.d.ts +6 -0
  285. package/dist/lib/util/hostname.d.ts.map +1 -0
  286. package/dist/lib/util/hostname.js +24 -0
  287. package/dist/lib/util/hostname.js.map +1 -0
  288. package/dist/lib/util/redirect-uri.d.ts +7 -0
  289. package/dist/lib/util/redirect-uri.d.ts.map +1 -0
  290. package/dist/lib/util/redirect-uri.js +44 -0
  291. package/dist/lib/util/redirect-uri.js.map +1 -0
  292. package/dist/lib/util/time.d.ts +6 -0
  293. package/dist/lib/util/time.d.ts.map +1 -0
  294. package/dist/lib/util/time.js +28 -0
  295. package/dist/lib/util/time.js.map +1 -0
  296. package/dist/lib/util/type.d.ts +6 -0
  297. package/dist/lib/util/type.d.ts.map +1 -0
  298. package/dist/lib/util/type.js +3 -0
  299. package/dist/lib/util/type.js.map +1 -0
  300. package/dist/lib/util/well-known.d.ts +3 -0
  301. package/dist/lib/util/well-known.d.ts.map +1 -0
  302. package/dist/lib/util/well-known.js +11 -0
  303. package/dist/lib/util/well-known.js.map +1 -0
  304. package/dist/metadata/build-metadata.d.ts +14 -0
  305. package/dist/metadata/build-metadata.d.ts.map +1 -0
  306. package/dist/metadata/build-metadata.js +132 -0
  307. package/dist/metadata/build-metadata.js.map +1 -0
  308. package/dist/oauth-client.d.ts +4 -0
  309. package/dist/oauth-client.d.ts.map +1 -0
  310. package/dist/oauth-client.js +19 -0
  311. package/dist/oauth-client.js.map +1 -0
  312. package/dist/oauth-dpop.d.ts +3 -0
  313. package/dist/oauth-dpop.d.ts.map +1 -0
  314. package/dist/oauth-dpop.js +19 -0
  315. package/dist/oauth-dpop.js.map +1 -0
  316. package/dist/oauth-errors.d.ts +20 -0
  317. package/dist/oauth-errors.d.ts.map +1 -0
  318. package/dist/oauth-errors.js +43 -0
  319. package/dist/oauth-errors.js.map +1 -0
  320. package/dist/oauth-hooks.d.ts +42 -0
  321. package/dist/oauth-hooks.d.ts.map +1 -0
  322. package/dist/oauth-hooks.js +3 -0
  323. package/dist/oauth-hooks.js.map +1 -0
  324. package/dist/oauth-provider.d.ts +179 -0
  325. package/dist/oauth-provider.d.ts.map +1 -0
  326. package/dist/oauth-provider.js +748 -0
  327. package/dist/oauth-provider.js.map +1 -0
  328. package/dist/oauth-store.d.ts +11 -0
  329. package/dist/oauth-store.d.ts.map +1 -0
  330. package/dist/oauth-store.js +27 -0
  331. package/dist/oauth-store.js.map +1 -0
  332. package/dist/oauth-verifier.d.ts +66 -0
  333. package/dist/oauth-verifier.d.ts.map +1 -0
  334. package/dist/oauth-verifier.js +94 -0
  335. package/dist/oauth-verifier.js.map +1 -0
  336. package/dist/oidc/claims.d.ts +16 -0
  337. package/dist/oidc/claims.d.ts.map +1 -0
  338. package/dist/oidc/claims.js +29 -0
  339. package/dist/oidc/claims.js.map +1 -0
  340. package/dist/oidc/sub.d.ts +4 -0
  341. package/dist/oidc/sub.d.ts.map +1 -0
  342. package/dist/oidc/sub.js +6 -0
  343. package/dist/oidc/sub.js.map +1 -0
  344. package/dist/oidc/userinfo.d.ts +7 -0
  345. package/dist/oidc/userinfo.d.ts.map +1 -0
  346. package/dist/oidc/userinfo.js +3 -0
  347. package/dist/oidc/userinfo.js.map +1 -0
  348. package/dist/output/build-error-payload.d.ts +6 -0
  349. package/dist/output/build-error-payload.d.ts.map +1 -0
  350. package/dist/output/build-error-payload.js +108 -0
  351. package/dist/output/build-error-payload.js.map +1 -0
  352. package/dist/output/customization.d.ts +37 -0
  353. package/dist/output/customization.d.ts.map +1 -0
  354. package/dist/output/customization.js +62 -0
  355. package/dist/output/customization.js.map +1 -0
  356. package/dist/output/send-authorize-page.d.ts +43 -0
  357. package/dist/output/send-authorize-page.d.ts.map +1 -0
  358. package/dist/output/send-authorize-page.js +49 -0
  359. package/dist/output/send-authorize-page.js.map +1 -0
  360. package/dist/output/send-authorize-redirect.d.ts +25 -0
  361. package/dist/output/send-authorize-redirect.d.ts.map +1 -0
  362. package/dist/output/send-authorize-redirect.js +72 -0
  363. package/dist/output/send-authorize-redirect.js.map +1 -0
  364. package/dist/output/send-error-page.d.ts +5 -0
  365. package/dist/output/send-error-page.d.ts.map +1 -0
  366. package/dist/output/send-error-page.js +31 -0
  367. package/dist/output/send-error-page.js.map +1 -0
  368. package/dist/output/send-web-page.d.ts +8 -0
  369. package/dist/output/send-web-page.d.ts.map +1 -0
  370. package/dist/output/send-web-page.js +48 -0
  371. package/dist/output/send-web-page.js.map +1 -0
  372. package/dist/parameters/claims-requested.d.ts +3 -0
  373. package/dist/parameters/claims-requested.d.ts.map +1 -0
  374. package/dist/parameters/claims-requested.js +77 -0
  375. package/dist/parameters/claims-requested.js.map +1 -0
  376. package/dist/parameters/oidc-payload.d.ts +31 -0
  377. package/dist/parameters/oidc-payload.d.ts.map +1 -0
  378. package/dist/parameters/oidc-payload.js +25 -0
  379. package/dist/parameters/oidc-payload.js.map +1 -0
  380. package/dist/replay/replay-manager.d.ts +10 -0
  381. package/dist/replay/replay-manager.d.ts.map +1 -0
  382. package/dist/replay/replay-manager.js +23 -0
  383. package/dist/replay/replay-manager.js.map +1 -0
  384. package/dist/replay/replay-store-memory.d.ts +11 -0
  385. package/dist/replay/replay-store-memory.d.ts.map +1 -0
  386. package/dist/replay/replay-store-memory.js +30 -0
  387. package/dist/replay/replay-store-memory.js.map +1 -0
  388. package/dist/replay/replay-store-redis.d.ts +16 -0
  389. package/dist/replay/replay-store-redis.d.ts.map +1 -0
  390. package/dist/replay/replay-store-redis.js +20 -0
  391. package/dist/replay/replay-store-redis.js.map +1 -0
  392. package/dist/replay/replay-store.d.ts +16 -0
  393. package/dist/replay/replay-store.d.ts.map +1 -0
  394. package/dist/replay/replay-store.js +22 -0
  395. package/dist/replay/replay-store.js.map +1 -0
  396. package/dist/request/code.d.ts +7 -0
  397. package/dist/request/code.d.ts.map +1 -0
  398. package/dist/request/code.js +20 -0
  399. package/dist/request/code.js.map +1 -0
  400. package/dist/request/request-data.d.ts +21 -0
  401. package/dist/request/request-data.d.ts.map +1 -0
  402. package/dist/request/request-data.js +6 -0
  403. package/dist/request/request-data.js.map +1 -0
  404. package/dist/request/request-id.d.ts +6 -0
  405. package/dist/request/request-id.d.ts.map +1 -0
  406. package/dist/request/request-id.js +18 -0
  407. package/dist/request/request-id.js.map +1 -0
  408. package/dist/request/request-info.d.ts +12 -0
  409. package/dist/request/request-info.d.ts.map +1 -0
  410. package/dist/request/request-info.js +3 -0
  411. package/dist/request/request-info.js.map +1 -0
  412. package/dist/request/request-manager.d.ts +40 -0
  413. package/dist/request/request-manager.d.ts.map +1 -0
  414. package/dist/request/request-manager.js +310 -0
  415. package/dist/request/request-manager.js.map +1 -0
  416. package/dist/request/request-store-memory.d.ts +16 -0
  417. package/dist/request/request-store-memory.d.ts.map +1 -0
  418. package/dist/request/request-store-memory.js +31 -0
  419. package/dist/request/request-store-memory.js.map +1 -0
  420. package/dist/request/request-store-redis.d.ts +24 -0
  421. package/dist/request/request-store-redis.d.ts.map +1 -0
  422. package/dist/request/request-store-redis.js +58 -0
  423. package/dist/request/request-store-redis.js.map +1 -0
  424. package/dist/request/request-store.d.ts +27 -0
  425. package/dist/request/request-store.d.ts.map +1 -0
  426. package/dist/request/request-store.js +37 -0
  427. package/dist/request/request-store.js.map +1 -0
  428. package/dist/request/request-uri.d.ts +8 -0
  429. package/dist/request/request-uri.d.ts.map +1 -0
  430. package/dist/request/request-uri.js +24 -0
  431. package/dist/request/request-uri.js.map +1 -0
  432. package/dist/request/types.d.ts +328 -0
  433. package/dist/request/types.d.ts.map +1 -0
  434. package/dist/request/types.js +27 -0
  435. package/dist/request/types.js.map +1 -0
  436. package/dist/signer/signed-token-payload.d.ts +1694 -0
  437. package/dist/signer/signed-token-payload.d.ts.map +1 -0
  438. package/dist/signer/signed-token-payload.js +32 -0
  439. package/dist/signer/signed-token-payload.js.map +1 -0
  440. package/dist/signer/signer.d.ts +193 -0
  441. package/dist/signer/signer.d.ts.map +1 -0
  442. package/dist/signer/signer.js +101 -0
  443. package/dist/signer/signer.js.map +1 -0
  444. package/dist/token/refresh-token.d.ts +7 -0
  445. package/dist/token/refresh-token.d.ts.map +1 -0
  446. package/dist/token/refresh-token.js +20 -0
  447. package/dist/token/refresh-token.js.map +1 -0
  448. package/dist/token/token-claims.d.ts +1687 -0
  449. package/dist/token/token-claims.d.ts.map +1 -0
  450. package/dist/token/token-claims.js +30 -0
  451. package/dist/token/token-claims.js.map +1 -0
  452. package/dist/token/token-data.d.ts +20 -0
  453. package/dist/token/token-data.d.ts.map +1 -0
  454. package/dist/token/token-data.js +3 -0
  455. package/dist/token/token-data.js.map +1 -0
  456. package/dist/token/token-id.d.ts +7 -0
  457. package/dist/token/token-id.d.ts.map +1 -0
  458. package/dist/token/token-id.js +20 -0
  459. package/dist/token/token-id.js.map +1 -0
  460. package/dist/token/token-manager.d.ts +48 -0
  461. package/dist/token/token-manager.d.ts.map +1 -0
  462. package/dist/token/token-manager.js +421 -0
  463. package/dist/token/token-manager.js.map +1 -0
  464. package/dist/token/token-store.d.ts +35 -0
  465. package/dist/token/token-store.d.ts.map +1 -0
  466. package/dist/token/token-store.js +38 -0
  467. package/dist/token/token-store.js.map +1 -0
  468. package/dist/token/types.d.ts +250 -0
  469. package/dist/token/types.d.ts.map +1 -0
  470. package/dist/token/types.js +36 -0
  471. package/dist/token/types.js.map +1 -0
  472. package/dist/token/verify-token-claims.d.ts +17 -0
  473. package/dist/token/verify-token-claims.d.ts.map +1 -0
  474. package/dist/token/verify-token-claims.js +39 -0
  475. package/dist/token/verify-token-claims.js.map +1 -0
  476. package/package.json +83 -0
  477. package/rollup.config.js +55 -0
  478. package/src/access-token/access-token-type.ts +5 -0
  479. package/src/account/account-manager.ts +55 -0
  480. package/src/account/account-store.ts +74 -0
  481. package/src/account/account.ts +10 -0
  482. package/src/assets/app/app.tsx +28 -0
  483. package/src/assets/app/backend-data.ts +65 -0
  484. package/src/assets/app/components/accept-form.tsx +112 -0
  485. package/src/assets/app/components/account-identifier.tsx +18 -0
  486. package/src/assets/app/components/account-picker.tsx +108 -0
  487. package/src/assets/app/components/client-identifier.tsx +32 -0
  488. package/src/assets/app/components/client-name.tsx +30 -0
  489. package/src/assets/app/components/error-card.tsx +41 -0
  490. package/src/assets/app/components/help-card.tsx +42 -0
  491. package/src/assets/app/components/layout-title-page.tsx +43 -0
  492. package/src/assets/app/components/layout-welcome.tsx +58 -0
  493. package/src/assets/app/components/sign-in-form.tsx +290 -0
  494. package/src/assets/app/components/sign-up-account-form.tsx +210 -0
  495. package/src/assets/app/components/sign-up-disclaimer.tsx +44 -0
  496. package/src/assets/app/components/url-viewer.tsx +70 -0
  497. package/src/assets/app/cookies.ts +11 -0
  498. package/src/assets/app/hooks/use-api.ts +104 -0
  499. package/src/assets/app/hooks/use-bound-dispatch.ts +5 -0
  500. package/src/assets/app/hooks/use-csrf-token.ts +5 -0
  501. package/src/assets/app/lib/api.ts +64 -0
  502. package/src/assets/app/lib/clsx.ts +4 -0
  503. package/src/assets/app/lib/util.ts +10 -0
  504. package/src/assets/app/main.css +11 -0
  505. package/src/assets/app/main.tsx +28 -0
  506. package/src/assets/app/views/accept-view.tsx +51 -0
  507. package/src/assets/app/views/authorize-view.tsx +101 -0
  508. package/src/assets/app/views/error-view.tsx +27 -0
  509. package/src/assets/app/views/sign-in-view.tsx +121 -0
  510. package/src/assets/app/views/sign-up-view.tsx +93 -0
  511. package/src/assets/app/views/welcome-view.tsx +61 -0
  512. package/src/assets/asset.ts +8 -0
  513. package/src/assets/assets-middleware.ts +32 -0
  514. package/src/assets/index.ts +74 -0
  515. package/src/client/client-auth.ts +45 -0
  516. package/src/client/client-data.ts +9 -0
  517. package/src/client/client-id.ts +4 -0
  518. package/src/client/client-info.ts +13 -0
  519. package/src/client/client-manager.ts +818 -0
  520. package/src/client/client-store.ts +38 -0
  521. package/src/client/client-utils.ts +43 -0
  522. package/src/client/client.ts +231 -0
  523. package/src/constants.ts +69 -0
  524. package/src/device/device-data.ts +11 -0
  525. package/src/device/device-details.ts +43 -0
  526. package/src/device/device-id.ts +23 -0
  527. package/src/device/device-manager.ts +287 -0
  528. package/src/device/device-store.ts +35 -0
  529. package/src/device/session-id.ts +22 -0
  530. package/src/dpop/dpop-manager.ts +147 -0
  531. package/src/dpop/dpop-nonce.ts +104 -0
  532. package/src/errors/access-denied-error.ts +26 -0
  533. package/src/errors/account-selection-required-error.ts +12 -0
  534. package/src/errors/consent-required-error.ts +12 -0
  535. package/src/errors/invalid-authorization-details-error.ts +22 -0
  536. package/src/errors/invalid-client-error.ts +20 -0
  537. package/src/errors/invalid-client-id-error.ts +20 -0
  538. package/src/errors/invalid-client-metadata-error.ts +19 -0
  539. package/src/errors/invalid-dpop-key-binding-error.ts +21 -0
  540. package/src/errors/invalid-dpop-proof-error.ts +13 -0
  541. package/src/errors/invalid-grant-error.ts +16 -0
  542. package/src/errors/invalid-parameters-error.ts +12 -0
  543. package/src/errors/invalid-redirect-uri-error.ts +17 -0
  544. package/src/errors/invalid-request-error.ts +30 -0
  545. package/src/errors/invalid-token-error.ts +59 -0
  546. package/src/errors/login-required-error.ts +12 -0
  547. package/src/errors/oauth-error.ts +28 -0
  548. package/src/errors/unauthorized-client-error.ts +20 -0
  549. package/src/errors/use-dpop-nonce-error.ts +32 -0
  550. package/src/errors/www-authenticate-error.ts +65 -0
  551. package/src/index.ts +15 -0
  552. package/src/lib/html/README.md +9 -0
  553. package/src/lib/html/build-document.ts +98 -0
  554. package/src/lib/html/escapers.ts +66 -0
  555. package/src/lib/html/html.ts +61 -0
  556. package/src/lib/html/index.ts +5 -0
  557. package/src/lib/html/tags.ts +58 -0
  558. package/src/lib/html/util.ts +21 -0
  559. package/src/lib/http/README.md +11 -0
  560. package/src/lib/http/accept.ts +91 -0
  561. package/src/lib/http/context.ts +11 -0
  562. package/src/lib/http/index.ts +9 -0
  563. package/src/lib/http/method.ts +18 -0
  564. package/src/lib/http/middleware.ts +183 -0
  565. package/src/lib/http/parser.ts +64 -0
  566. package/src/lib/http/path.ts +82 -0
  567. package/src/lib/http/request.ts +141 -0
  568. package/src/lib/http/response.ts +133 -0
  569. package/src/lib/http/route.ts +56 -0
  570. package/src/lib/http/router.ts +118 -0
  571. package/src/lib/http/stream.ts +78 -0
  572. package/src/lib/http/types.ts +22 -0
  573. package/src/lib/http/url.ts +23 -0
  574. package/src/lib/redis.ts +23 -0
  575. package/src/lib/util/authorization-header.ts +26 -0
  576. package/src/lib/util/cast.ts +4 -0
  577. package/src/lib/util/crypto.ts +27 -0
  578. package/src/lib/util/date.ts +7 -0
  579. package/src/lib/util/hostname.ts +19 -0
  580. package/src/lib/util/redirect-uri.ts +46 -0
  581. package/src/lib/util/time.ts +33 -0
  582. package/src/lib/util/type.ts +4 -0
  583. package/src/lib/util/well-known.ts +8 -0
  584. package/src/metadata/build-metadata.ts +165 -0
  585. package/src/oauth-client.ts +3 -0
  586. package/src/oauth-dpop.ts +2 -0
  587. package/src/oauth-errors.ts +21 -0
  588. package/src/oauth-hooks.ts +66 -0
  589. package/src/oauth-provider.ts +1409 -0
  590. package/src/oauth-store.ts +11 -0
  591. package/src/oauth-verifier.ts +219 -0
  592. package/src/oidc/claims.ts +35 -0
  593. package/src/oidc/sub.ts +4 -0
  594. package/src/oidc/userinfo.ts +11 -0
  595. package/src/output/build-error-payload.ts +143 -0
  596. package/src/output/customization.ts +96 -0
  597. package/src/output/send-authorize-page.ts +111 -0
  598. package/src/output/send-authorize-redirect.ts +130 -0
  599. package/src/output/send-error-page.ts +41 -0
  600. package/src/output/send-web-page.ts +66 -0
  601. package/src/parameters/claims-requested.ts +106 -0
  602. package/src/parameters/oidc-payload.ts +28 -0
  603. package/src/replay/replay-manager.ts +38 -0
  604. package/src/replay/replay-store-memory.ts +36 -0
  605. package/src/replay/replay-store-redis.ts +31 -0
  606. package/src/replay/replay-store.ts +44 -0
  607. package/src/request/code.ts +24 -0
  608. package/src/request/request-data.ts +26 -0
  609. package/src/request/request-id.ts +23 -0
  610. package/src/request/request-info.ts +12 -0
  611. package/src/request/request-manager.ts +479 -0
  612. package/src/request/request-store-memory.ts +39 -0
  613. package/src/request/request-store-redis.ts +71 -0
  614. package/src/request/request-store.ts +54 -0
  615. package/src/request/request-uri.ts +29 -0
  616. package/src/request/types.ts +48 -0
  617. package/src/signer/signed-token-payload.ts +35 -0
  618. package/src/signer/signer.ts +165 -0
  619. package/src/token/refresh-token.ts +31 -0
  620. package/src/token/token-claims.ts +31 -0
  621. package/src/token/token-data.ts +33 -0
  622. package/src/token/token-id.ts +26 -0
  623. package/src/token/token-manager.ts +591 -0
  624. package/src/token/token-store.ts +78 -0
  625. package/src/token/types.ts +86 -0
  626. package/src/token/verify-token-claims.ts +65 -0
  627. package/tailwind.config.js +13 -0
  628. package/tsconfig.backend.json +9 -0
  629. package/tsconfig.frontend.json +11 -0
  630. package/tsconfig.json +8 -0
  631. package/tsconfig.tools.json +8 -0
@@ -0,0 +1,1409 @@
1
+ import { safeFetchWrap } from '@atproto-labs/fetch-node'
2
+ import { SimpleStore } from '@atproto-labs/simple-store'
3
+ import { SimpleStoreMemory } from '@atproto-labs/simple-store-memory'
4
+ import { Jwks, Keyset, SignedJwt, signedJwtSchema } from '@atproto/jwk'
5
+ import {
6
+ AccessToken,
7
+ CLIENT_ASSERTION_TYPE_JWT_BEARER,
8
+ OAuthAuthenticationRequestParameters,
9
+ OAuthAuthorizationServerMetadata,
10
+ OAuthClientIdentification,
11
+ OAuthClientMetadata,
12
+ OAuthEndpointName,
13
+ OAuthTokenResponse,
14
+ OAuthTokenType,
15
+ atprotoLoopbackClientMetadata,
16
+ oauthAuthenticationRequestParametersSchema,
17
+ } from '@atproto/oauth-types'
18
+ import { Redis, type RedisOptions } from 'ioredis'
19
+ import { z } from 'zod'
20
+
21
+ import { AccessTokenType } from './access-token/access-token-type.js'
22
+ import { AccountManager } from './account/account-manager.js'
23
+ import {
24
+ AccountInfo,
25
+ AccountStore,
26
+ DeviceAccountInfo,
27
+ LoginCredentials,
28
+ asAccountStore,
29
+ } from './account/account-store.js'
30
+ import { Account } from './account/account.js'
31
+ import { authorizeAssetsMiddleware } from './assets/assets-middleware.js'
32
+ import { ClientAuth, authJwkThumbprint } from './client/client-auth.js'
33
+ import { ClientId, clientIdSchema } from './client/client-id.js'
34
+ import {
35
+ ClientManager,
36
+ LoopbackMetadataGetter,
37
+ } from './client/client-manager.js'
38
+ import { ClientStore, ifClientStore } from './client/client-store.js'
39
+ import { Client } from './client/client.js'
40
+ import { AUTHENTICATION_MAX_AGE, TOKEN_MAX_AGE } from './constants.js'
41
+ import { DeviceId } from './device/device-id.js'
42
+ import { DeviceManager } from './device/device-manager.js'
43
+ import { DeviceStore, asDeviceStore } from './device/device-store.js'
44
+ import { AccessDeniedError } from './errors/access-denied-error.js'
45
+ import { AccountSelectionRequiredError } from './errors/account-selection-required-error.js'
46
+ import { ConsentRequiredError } from './errors/consent-required-error.js'
47
+ import { InvalidClientError } from './errors/invalid-client-error.js'
48
+ import { InvalidGrantError } from './errors/invalid-grant-error.js'
49
+ import { InvalidParametersError } from './errors/invalid-parameters-error.js'
50
+ import { InvalidRequestError } from './errors/invalid-request-error.js'
51
+ import { LoginRequiredError } from './errors/login-required-error.js'
52
+ import { OAuthError } from './errors/oauth-error.js'
53
+ import { UnauthorizedClientError } from './errors/unauthorized-client-error.js'
54
+ import { WWWAuthenticateError } from './errors/www-authenticate-error.js'
55
+ import {
56
+ Handler,
57
+ IncomingMessage,
58
+ Middleware,
59
+ Router,
60
+ ServerResponse,
61
+ acceptMiddleware,
62
+ combineMiddlewares,
63
+ setupCsrfToken,
64
+ staticJsonHandler,
65
+ validateCsrfToken,
66
+ validateFetchMode,
67
+ validateReferer,
68
+ validateRequestPayload,
69
+ validateSameOrigin,
70
+ writeJson,
71
+ } from './lib/http/index.js'
72
+ import { dateToEpoch, dateToRelativeSeconds } from './lib/util/date.js'
73
+ import { Override } from './lib/util/type.js'
74
+ import { CustomMetadata, buildMetadata } from './metadata/build-metadata.js'
75
+ import { OAuthHooks } from './oauth-hooks.js'
76
+ import { OAuthVerifier, OAuthVerifierOptions } from './oauth-verifier.js'
77
+ import { Userinfo } from './oidc/userinfo.js'
78
+ import {
79
+ buildErrorPayload,
80
+ buildErrorStatus,
81
+ } from './output/build-error-payload.js'
82
+ import { Customization } from './output/customization.js'
83
+ import {
84
+ AuthorizationResultAuthorize,
85
+ sendAuthorizePage,
86
+ } from './output/send-authorize-page.js'
87
+ import {
88
+ AuthorizationResultRedirect,
89
+ sendAuthorizeRedirect,
90
+ } from './output/send-authorize-redirect.js'
91
+ import { sendErrorPage } from './output/send-error-page.js'
92
+ import { oidcPayload } from './parameters/oidc-payload.js'
93
+ import { ReplayStore, ifReplayStore } from './replay/replay-store.js'
94
+ import { RequestInfo } from './request/request-info.js'
95
+ import { RequestManager } from './request/request-manager.js'
96
+ import { RequestStoreMemory } from './request/request-store-memory.js'
97
+ import { RequestStoreRedis } from './request/request-store-redis.js'
98
+ import { RequestStore, ifRequestStore } from './request/request-store.js'
99
+ import { RequestUri, requestUriSchema } from './request/request-uri.js'
100
+ import {
101
+ AuthorizationRequestJar,
102
+ AuthorizationRequestQuery,
103
+ PushedAuthorizationRequest,
104
+ authorizationRequestQuerySchema,
105
+ pushedAuthorizationRequestSchema,
106
+ } from './request/types.js'
107
+ import { isTokenId } from './token/token-id.js'
108
+ import { TokenManager } from './token/token-manager.js'
109
+ import { TokenInfo, TokenStore, asTokenStore } from './token/token-store.js'
110
+ import {
111
+ CodeGrantRequest,
112
+ Introspect,
113
+ IntrospectionResponse,
114
+ RefreshGrantRequest,
115
+ Revoke,
116
+ TokenRequest,
117
+ introspectSchema,
118
+ revokeSchema,
119
+ tokenRequestSchema,
120
+ } from './token/types.js'
121
+ import { VerifyTokenClaimsOptions } from './token/verify-token-claims.js'
122
+
123
+ export type OAuthProviderStore = Partial<
124
+ ClientStore &
125
+ AccountStore &
126
+ DeviceStore &
127
+ TokenStore &
128
+ RequestStore &
129
+ ReplayStore
130
+ >
131
+
132
+ export {
133
+ Keyset,
134
+ type CustomMetadata,
135
+ type Customization,
136
+ type Handler,
137
+ type OAuthAuthorizationServerMetadata,
138
+ }
139
+
140
+ export type RouterOptions<
141
+ Req extends IncomingMessage = IncomingMessage,
142
+ Res extends ServerResponse = ServerResponse,
143
+ > = {
144
+ onError?: (req: Req, res: Res, err: unknown, message: string) => void
145
+ }
146
+
147
+ export type OAuthProviderOptions = Override<
148
+ OAuthVerifierOptions & OAuthHooks,
149
+ {
150
+ /**
151
+ * Maximum age a device/account session can be before requiring
152
+ * re-authentication. This can be overridden on a authorization request basis
153
+ * using the `max_age` parameter and on a client basis using the
154
+ * `default_max_age` client metadata.
155
+ */
156
+ authenticationMaxAge?: number
157
+
158
+ /**
159
+ * Maximum age access & id tokens can be before requiring a refresh.
160
+ */
161
+ tokenMaxAge?: number
162
+
163
+ /**
164
+ * Additional metadata to be included in the discovery document.
165
+ */
166
+ metadata?: CustomMetadata
167
+
168
+ /**
169
+ * UI customizations
170
+ */
171
+ customization?: Customization
172
+
173
+ /**
174
+ * A custom fetch function that can be used to fetch the client metadata from
175
+ * the internet. By default, the fetch function is a safeFetchWrap() function
176
+ * that protects against SSRF attacks, large responses & known bad domains. If
177
+ * you want to disable all protections, you can provide `globalThis.fetch` as
178
+ * fetch function.
179
+ */
180
+ safeFetch?: typeof globalThis.fetch
181
+
182
+ /**
183
+ * A redis instance to use for replay protection. If not provided, replay
184
+ * protection will use memory storage.
185
+ */
186
+ redis?: Redis | RedisOptions | string
187
+
188
+ /**
189
+ * This will be used as the default store for all the stores. If a store is
190
+ * not provided, this store will be used instead. If the `store` does not
191
+ * implement a specific store, a runtime error will be thrown. Make sure that
192
+ * this store implements all the interfaces not provided in the other
193
+ * `<name>Store` options.
194
+ */
195
+ store?: OAuthProviderStore
196
+
197
+ accountStore?: AccountStore
198
+ deviceStore?: DeviceStore
199
+ clientStore?: ClientStore
200
+ replayStore?: ReplayStore
201
+ requestStore?: RequestStore
202
+ tokenStore?: TokenStore
203
+
204
+ /**
205
+ * In order to speed up the client fetching process, you can provide a cache
206
+ * to store HTTP responses.
207
+ *
208
+ * @note the cached entries should automatically expire after a certain time (typically 10 minutes)
209
+ */
210
+ clientJwksCache?: SimpleStore<string, Jwks>
211
+
212
+ /**
213
+ * In order to speed up the client fetching process, you can provide a cache
214
+ * to store HTTP responses.
215
+ *
216
+ * @note the cached entries should automatically expire after a certain time (typically 10 minutes)
217
+ */
218
+ clientMetadataCache?: SimpleStore<string, OAuthClientMetadata>
219
+
220
+ /**
221
+ * In order to enable loopback clients, you can provide a function that
222
+ * returns the client metadata for a given loopback URL. This is useful for
223
+ * development and testing purposes. This function is not called for internet
224
+ * clients.
225
+ *
226
+ * @default is as specified by ATPROTO
227
+ */
228
+ loopbackMetadata?: null | false | LoopbackMetadataGetter
229
+ }
230
+ >
231
+
232
+ export class OAuthProvider extends OAuthVerifier {
233
+ public readonly metadata: OAuthAuthorizationServerMetadata
234
+ public readonly customization?: Customization
235
+
236
+ public readonly authenticationMaxAge: number
237
+
238
+ public readonly accountManager: AccountManager
239
+ public readonly deviceStore: DeviceStore
240
+ public readonly clientManager: ClientManager
241
+ public readonly requestManager: RequestManager
242
+ public readonly tokenManager: TokenManager
243
+
244
+ public constructor({
245
+ metadata,
246
+ customization = undefined,
247
+ authenticationMaxAge = AUTHENTICATION_MAX_AGE,
248
+ tokenMaxAge = TOKEN_MAX_AGE,
249
+
250
+ safeFetch = safeFetchWrap(),
251
+ redis,
252
+ store, // compound store implementation
253
+
254
+ // Requires stores
255
+ accountStore = asAccountStore(store),
256
+ deviceStore = asDeviceStore(store),
257
+ tokenStore = asTokenStore(store),
258
+
259
+ // These are optional
260
+ clientStore = ifClientStore(store),
261
+ replayStore = ifReplayStore(store),
262
+ requestStore = ifRequestStore(store),
263
+
264
+ clientJwksCache = new SimpleStoreMemory({
265
+ maxSize: 50_000_000,
266
+ ttl: 600e3,
267
+ }),
268
+ clientMetadataCache = new SimpleStoreMemory({
269
+ maxSize: 50_000_000,
270
+ ttl: 600e3,
271
+ }),
272
+
273
+ loopbackMetadata = atprotoLoopbackClientMetadata,
274
+
275
+ // OAuthHooks & OAuthVerifierOptions
276
+ ...rest
277
+ }: OAuthProviderOptions) {
278
+ super({ replayStore, redis, ...rest })
279
+
280
+ requestStore ??= redis
281
+ ? new RequestStoreRedis({ redis })
282
+ : new RequestStoreMemory()
283
+
284
+ this.authenticationMaxAge = authenticationMaxAge
285
+ this.metadata = buildMetadata(this.issuer, this.keyset, metadata)
286
+ this.customization = customization
287
+
288
+ this.deviceStore = deviceStore
289
+
290
+ this.accountManager = new AccountManager(accountStore)
291
+ this.clientManager = new ClientManager(
292
+ this.keyset,
293
+ rest,
294
+ clientStore || null,
295
+ loopbackMetadata || null,
296
+ safeFetch,
297
+ clientJwksCache,
298
+ clientMetadataCache,
299
+ )
300
+ this.requestManager = new RequestManager(
301
+ requestStore,
302
+ this.signer,
303
+ this.metadata,
304
+ rest,
305
+ )
306
+ this.tokenManager = new TokenManager(
307
+ tokenStore,
308
+ this.signer,
309
+ rest,
310
+ this.accessTokenType,
311
+ tokenMaxAge,
312
+ )
313
+ }
314
+
315
+ get jwks(): Jwks {
316
+ return this.keyset.publicJwks
317
+ }
318
+
319
+ protected loginRequired(
320
+ client: Client,
321
+ parameters: OAuthAuthenticationRequestParameters,
322
+ info: DeviceAccountInfo,
323
+ ) {
324
+ /** in seconds */
325
+ const authAge = (Date.now() - info.authenticatedAt.getTime()) / 1e3
326
+
327
+ // Fool-proof (invalid date, or suspiciously in the future)
328
+ if (!Number.isFinite(authAge) || authAge < 0) {
329
+ return true
330
+ }
331
+
332
+ /** in seconds */
333
+ const maxAge = parameters.max_age ?? client.metadata.default_max_age
334
+
335
+ if (maxAge != null && maxAge < this.authenticationMaxAge) {
336
+ return authAge >= maxAge
337
+ } else {
338
+ return authAge >= this.authenticationMaxAge
339
+ }
340
+ }
341
+
342
+ protected async authenticateClient(
343
+ client: Client,
344
+ endpoint: OAuthEndpointName,
345
+ credentials: OAuthClientIdentification,
346
+ ): Promise<ClientAuth> {
347
+ const { clientAuth, nonce } = await client.verifyCredentials(
348
+ credentials,
349
+ endpoint,
350
+ { audience: this.issuer },
351
+ )
352
+
353
+ if (nonce != null) {
354
+ const unique = await this.replayManager.uniqueAuth(nonce, client.id)
355
+ if (!unique) {
356
+ throw new InvalidClientError(`${clientAuth.method} jti reused`)
357
+ }
358
+ }
359
+
360
+ return clientAuth
361
+ }
362
+
363
+ protected async decodeJAR(
364
+ client: Client,
365
+ input: AuthorizationRequestJar,
366
+ ): Promise<
367
+ | {
368
+ payload: OAuthAuthenticationRequestParameters
369
+ }
370
+ | {
371
+ payload: OAuthAuthenticationRequestParameters
372
+ protectedHeader: { kid: string; alg: string }
373
+ jkt: string
374
+ }
375
+ > {
376
+ const result = await client.decodeRequestObject(input.request)
377
+ const payload = oauthAuthenticationRequestParametersSchema.parse(
378
+ result.payload,
379
+ )
380
+
381
+ if (!result.payload.jti) {
382
+ throw new InvalidParametersError(
383
+ payload,
384
+ 'Request object must contain a jti claim',
385
+ )
386
+ }
387
+
388
+ if (!(await this.replayManager.uniqueJar(result.payload.jti, client.id))) {
389
+ throw new InvalidParametersError(
390
+ payload,
391
+ 'Request object jti is not unique',
392
+ )
393
+ }
394
+
395
+ if ('protectedHeader' in result) {
396
+ if (!result.protectedHeader.kid) {
397
+ throw new InvalidParametersError(payload, 'Missing "kid" in header')
398
+ }
399
+
400
+ return {
401
+ jkt: await authJwkThumbprint(result.key),
402
+ payload,
403
+ protectedHeader: result.protectedHeader as {
404
+ alg: string
405
+ kid: string
406
+ },
407
+ }
408
+ }
409
+
410
+ if ('header' in result) {
411
+ return {
412
+ payload,
413
+ }
414
+ }
415
+
416
+ // Should never happen
417
+ throw new Error('Invalid request object')
418
+ }
419
+
420
+ /**
421
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc9126}
422
+ */
423
+ protected async pushedAuthorizationRequest(
424
+ input: PushedAuthorizationRequest,
425
+ dpopJkt: null | string,
426
+ ) {
427
+ try {
428
+ const client = await this.clientManager.getClient(input.client_id)
429
+ const clientAuth = await this.authenticateClient(
430
+ client,
431
+ 'pushed_authorization_request',
432
+ input,
433
+ )
434
+
435
+ const { payload: parameters } =
436
+ 'request' in input // Handle JAR
437
+ ? await this.decodeJAR(client, input)
438
+ : { payload: input }
439
+
440
+ const { uri, expiresAt } =
441
+ await this.requestManager.createAuthorizationRequest(
442
+ client,
443
+ clientAuth,
444
+ parameters,
445
+ null,
446
+ dpopJkt,
447
+ )
448
+
449
+ return {
450
+ request_uri: uri,
451
+ expires_in: dateToRelativeSeconds(expiresAt),
452
+ }
453
+ } catch (err) {
454
+ // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3-1
455
+ // > Since initial processing of the pushed authorization request does not
456
+ // > involve resource owner interaction, error codes related to user
457
+ // > interaction, such as consent_required defined by [OIDC], are never
458
+ // > returned.
459
+ if (err instanceof AccessDeniedError) {
460
+ throw new InvalidRequestError(err.error_description, err)
461
+ }
462
+ throw err
463
+ }
464
+ }
465
+
466
+ private async loadAuthorizationRequest(
467
+ client: Client,
468
+ deviceId: DeviceId,
469
+ input: AuthorizationRequestQuery,
470
+ ): Promise<RequestInfo> {
471
+ // Load PAR
472
+ if ('request_uri' in input) {
473
+ return this.requestManager.get(input.request_uri, client.id, deviceId)
474
+ }
475
+
476
+ // Handle JAR
477
+ if ('request' in input) {
478
+ const requestObject = await this.decodeJAR(client, input)
479
+
480
+ if ('protectedHeader' in requestObject && requestObject.protectedHeader) {
481
+ // Allow using signed JAR during "/authorize" as client authentication.
482
+ // This allows clients to skip PAR to initiate trusted sessions.
483
+ const clientAuth: ClientAuth = {
484
+ method: CLIENT_ASSERTION_TYPE_JWT_BEARER,
485
+ kid: requestObject.protectedHeader.kid,
486
+ alg: requestObject.protectedHeader.alg,
487
+ jkt: requestObject.jkt,
488
+ }
489
+
490
+ return this.requestManager.createAuthorizationRequest(
491
+ client,
492
+ clientAuth,
493
+ requestObject.payload,
494
+ deviceId,
495
+ null,
496
+ )
497
+ }
498
+
499
+ return this.requestManager.createAuthorizationRequest(
500
+ client,
501
+ { method: 'none' },
502
+ requestObject.payload,
503
+ deviceId,
504
+ null,
505
+ )
506
+ }
507
+
508
+ return this.requestManager.createAuthorizationRequest(
509
+ client,
510
+ { method: 'none' },
511
+ input,
512
+ deviceId,
513
+ null,
514
+ )
515
+ }
516
+
517
+ private async deleteRequest(
518
+ uri: RequestUri,
519
+ parameters: OAuthAuthenticationRequestParameters,
520
+ ) {
521
+ try {
522
+ await this.requestManager.delete(uri)
523
+ } catch (err) {
524
+ throw AccessDeniedError.from(parameters, err)
525
+ }
526
+ }
527
+
528
+ protected async authorize(
529
+ deviceId: DeviceId,
530
+ input: AuthorizationRequestQuery,
531
+ ): Promise<AuthorizationResultRedirect | AuthorizationResultAuthorize> {
532
+ const { issuer } = this
533
+ const client = await this.clientManager.getClient(input.client_id)
534
+
535
+ try {
536
+ const { uri, parameters, clientAuth } =
537
+ await this.loadAuthorizationRequest(client, deviceId, input)
538
+
539
+ try {
540
+ const sessions = await this.getSessions(
541
+ client,
542
+ clientAuth,
543
+ deviceId,
544
+ parameters,
545
+ )
546
+
547
+ if (parameters.prompt === 'none') {
548
+ const ssoSessions = sessions.filter((s) => s.matchesHint)
549
+ if (ssoSessions.length > 1) {
550
+ throw new AccountSelectionRequiredError(parameters)
551
+ }
552
+ if (ssoSessions.length < 1) {
553
+ throw new LoginRequiredError(parameters)
554
+ }
555
+
556
+ const ssoSession = ssoSessions[0]!
557
+ if (ssoSession.loginRequired) {
558
+ throw new LoginRequiredError(parameters)
559
+ }
560
+ if (ssoSession.consentRequired) {
561
+ throw new ConsentRequiredError(parameters)
562
+ }
563
+
564
+ const redirect = await this.requestManager.setAuthorized(
565
+ client,
566
+ uri,
567
+ deviceId,
568
+ ssoSession.account,
569
+ ssoSession.info,
570
+ )
571
+
572
+ return { issuer, client, parameters, redirect }
573
+ }
574
+
575
+ // Automatic SSO when a did was provided
576
+ if (parameters.prompt == null && parameters.login_hint != null) {
577
+ const ssoSessions = sessions.filter((s) => s.matchesHint)
578
+ if (ssoSessions.length === 1) {
579
+ const ssoSession = ssoSessions[0]!
580
+ if (!ssoSession.loginRequired && !ssoSession.consentRequired) {
581
+ const redirect = await this.requestManager.setAuthorized(
582
+ client,
583
+ uri,
584
+ deviceId,
585
+ ssoSession.account,
586
+ ssoSession.info,
587
+ )
588
+
589
+ return { issuer, client, parameters, redirect }
590
+ }
591
+ }
592
+ }
593
+
594
+ return {
595
+ issuer,
596
+ client,
597
+ parameters,
598
+ authorize: { uri, sessions },
599
+ }
600
+ } catch (err) {
601
+ await this.deleteRequest(uri, parameters)
602
+
603
+ // Transform into an AccessDeniedError to allow redirecting the user
604
+ // to the client with the error details.
605
+ throw AccessDeniedError.from(parameters, err)
606
+ }
607
+ } catch (err) {
608
+ if (err instanceof AccessDeniedError) {
609
+ return {
610
+ issuer,
611
+ client,
612
+ parameters: err.parameters,
613
+ redirect: err.toJSON(),
614
+ }
615
+ }
616
+
617
+ throw err
618
+ }
619
+ }
620
+
621
+ protected async getSessions(
622
+ client: Client,
623
+ clientAuth: ClientAuth,
624
+ deviceId: DeviceId,
625
+ parameters: OAuthAuthenticationRequestParameters,
626
+ ): Promise<
627
+ {
628
+ account: Account
629
+ info: DeviceAccountInfo
630
+
631
+ selected: boolean
632
+ loginRequired: boolean
633
+ consentRequired: boolean
634
+
635
+ matchesHint: boolean
636
+ }[]
637
+ > {
638
+ const accounts = await this.accountManager.list(deviceId)
639
+
640
+ return accounts.map(({ account, info }) => ({
641
+ account,
642
+ info,
643
+
644
+ selected:
645
+ parameters.prompt !== 'select_account' &&
646
+ parameters.login_hint === account.sub,
647
+ loginRequired:
648
+ parameters.prompt === 'login' ||
649
+ this.loginRequired(client, parameters, info),
650
+ consentRequired:
651
+ parameters.prompt === 'consent' ||
652
+ !info.authorizedClients.includes(client.id),
653
+
654
+ matchesHint:
655
+ parameters.login_hint === account.sub || parameters.login_hint == null,
656
+ }))
657
+ }
658
+
659
+ protected async signIn(
660
+ deviceId: DeviceId,
661
+ credentials: LoginCredentials,
662
+ ): Promise<AccountInfo> {
663
+ return this.accountManager.signIn(credentials, deviceId)
664
+ }
665
+
666
+ protected async acceptRequest(
667
+ deviceId: DeviceId,
668
+ uri: RequestUri,
669
+ clientId: ClientId,
670
+ sub: string,
671
+ ): Promise<AuthorizationResultRedirect> {
672
+ const { issuer } = this
673
+ const client = await this.clientManager.getClient(clientId)
674
+
675
+ try {
676
+ const { parameters, clientAuth } = await this.requestManager.get(
677
+ uri,
678
+ clientId,
679
+ deviceId,
680
+ )
681
+
682
+ try {
683
+ const { account, info } = await this.accountManager.get(deviceId, sub)
684
+
685
+ // The user is trying to authorize without a fresh login
686
+ if (this.loginRequired(client, parameters, info)) {
687
+ throw new LoginRequiredError(
688
+ parameters,
689
+ 'Account authentication required.',
690
+ )
691
+ }
692
+
693
+ const redirect = await this.requestManager.setAuthorized(
694
+ client,
695
+ uri,
696
+ deviceId,
697
+ account,
698
+ info,
699
+ )
700
+
701
+ await this.accountManager.addAuthorizedClient(
702
+ deviceId,
703
+ account,
704
+ client,
705
+ clientAuth,
706
+ )
707
+
708
+ return { issuer, client, parameters, redirect }
709
+ } catch (err) {
710
+ await this.deleteRequest(uri, parameters)
711
+
712
+ // throw AccessDeniedError.from(parameters, err)
713
+ throw err
714
+ }
715
+ } catch (err) {
716
+ if (err instanceof AccessDeniedError) {
717
+ const { parameters } = err
718
+ return { issuer, client, parameters, redirect: err.toJSON() }
719
+ }
720
+
721
+ throw err
722
+ }
723
+ }
724
+
725
+ protected async rejectRequest(
726
+ deviceId: DeviceId,
727
+ uri: RequestUri,
728
+ clientId: ClientId,
729
+ ): Promise<AuthorizationResultRedirect> {
730
+ try {
731
+ const { parameters } = await this.requestManager.get(
732
+ uri,
733
+ clientId,
734
+ deviceId,
735
+ )
736
+
737
+ await this.deleteRequest(uri, parameters)
738
+
739
+ // Trigger redirect (see catch block)
740
+ throw new AccessDeniedError(parameters, 'Access denied')
741
+ } catch (err) {
742
+ if (err instanceof AccessDeniedError) {
743
+ return {
744
+ issuer: this.issuer,
745
+ client: await this.clientManager.getClient(clientId),
746
+ parameters: err.parameters,
747
+ redirect: err.toJSON(),
748
+ }
749
+ }
750
+
751
+ throw err
752
+ }
753
+ }
754
+
755
+ protected async token(
756
+ input: TokenRequest,
757
+ dpopJkt: null | string,
758
+ ): Promise<OAuthTokenResponse> {
759
+ const client = await this.clientManager.getClient(input.client_id)
760
+ const clientAuth = await this.authenticateClient(client, 'token', input)
761
+
762
+ if (!client.metadata.grant_types.includes(input.grant_type)) {
763
+ throw new InvalidGrantError(
764
+ `"${input.grant_type}" grant type is not allowed for this client`,
765
+ )
766
+ }
767
+
768
+ if (input.grant_type === 'authorization_code') {
769
+ return this.codeGrant(client, clientAuth, input, dpopJkt)
770
+ }
771
+
772
+ if (input.grant_type === 'refresh_token') {
773
+ return this.refreshTokenGrant(client, clientAuth, input, dpopJkt)
774
+ }
775
+
776
+ throw new InvalidGrantError(
777
+ // @ts-expect-error: fool proof
778
+ `Grant type "${input.grant_type}" not supported`,
779
+ )
780
+ }
781
+
782
+ protected async codeGrant(
783
+ client: Client,
784
+ clientAuth: ClientAuth,
785
+ input: CodeGrantRequest,
786
+ dpopJkt: null | string,
787
+ ): Promise<OAuthTokenResponse> {
788
+ try {
789
+ const { sub, deviceId, parameters } = await this.requestManager.findCode(
790
+ client,
791
+ clientAuth,
792
+ input.code,
793
+ )
794
+
795
+ const { account, info } = await this.accountManager.get(deviceId, sub)
796
+
797
+ return await this.tokenManager.create(
798
+ client,
799
+ clientAuth,
800
+ account,
801
+ { id: deviceId, info },
802
+ parameters,
803
+ input,
804
+ dpopJkt,
805
+ )
806
+ } catch (err) {
807
+ // If a token is replayed, requestManager.findCode will throw. In that
808
+ // case, we need to revoke any token that was issued for this code.
809
+
810
+ await this.tokenManager.revoke(input.code)
811
+
812
+ // @TODO (?) in order to protect the user, we should maybe also mark the
813
+ // account-device association as expired ?
814
+
815
+ throw err
816
+ }
817
+ }
818
+
819
+ async refreshTokenGrant(
820
+ client: Client,
821
+ clientAuth: ClientAuth,
822
+ input: RefreshGrantRequest,
823
+ dpopJkt: null | string,
824
+ ): Promise<OAuthTokenResponse> {
825
+ return this.tokenManager.refresh(client, clientAuth, input, dpopJkt)
826
+ }
827
+
828
+ /**
829
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7009#section-2.1 rfc7009}
830
+ */
831
+ protected async revoke(input: Revoke) {
832
+ // @TODO this should also remove the account-device association (or, at
833
+ // least, mark it as expired)
834
+ await this.tokenManager.revoke(input.token)
835
+ }
836
+
837
+ /**
838
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc7662#section-2.1 rfc7662}
839
+ */
840
+ protected async introspect(
841
+ input: Introspect,
842
+ ): Promise<IntrospectionResponse> {
843
+ const client = await this.clientManager.getClient(input.client_id)
844
+ const clientAuth = await this.authenticateClient(
845
+ client,
846
+ 'introspection',
847
+ input,
848
+ )
849
+
850
+ // RFC7662 states the following:
851
+ //
852
+ // > To prevent token scanning attacks, the endpoint MUST also require some
853
+ // > form of authorization to access this endpoint, such as client
854
+ // > authentication as described in OAuth 2.0 [RFC6749] or a separate OAuth
855
+ // > 2.0 access token such as the bearer token described in OAuth 2.0 Bearer
856
+ // > Token Usage [RFC6750]. The methods of managing and validating these
857
+ // > authentication credentials are out of scope of this specification.
858
+ if (clientAuth.method === 'none') {
859
+ throw new UnauthorizedClientError('Client authentication required')
860
+ }
861
+
862
+ const start = Date.now()
863
+ try {
864
+ const tokenInfo = await this.tokenManager.clientTokenInfo(
865
+ client,
866
+ clientAuth,
867
+ input.token,
868
+ )
869
+
870
+ return {
871
+ active: true,
872
+
873
+ scope: tokenInfo.data.parameters.scope,
874
+ client_id: tokenInfo.data.clientId,
875
+ username: tokenInfo.account.preferred_username,
876
+ token_type: tokenInfo.data.parameters.dpop_jkt ? 'DPoP' : 'Bearer',
877
+ authorization_details: tokenInfo.data.details ?? undefined,
878
+
879
+ aud: tokenInfo.account.aud,
880
+ exp: dateToEpoch(tokenInfo.data.expiresAt),
881
+ iat: dateToEpoch(tokenInfo.data.updatedAt),
882
+ iss: this.signer.issuer,
883
+ jti: tokenInfo.id,
884
+ sub: tokenInfo.account.sub,
885
+ }
886
+ } catch (err) {
887
+ // Prevent brute force & timing attack (only for inactive tokens)
888
+ await new Promise((r) => setTimeout(r, 750 - (Date.now() - start)))
889
+
890
+ return {
891
+ active: false,
892
+ }
893
+ }
894
+ }
895
+
896
+ /**
897
+ * @see {@link https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.3.2 Successful UserInfo Response}
898
+ */
899
+ protected async userinfo({ data, account }: TokenInfo): Promise<Userinfo> {
900
+ return {
901
+ ...oidcPayload(data.parameters, account),
902
+
903
+ sub: account.sub,
904
+
905
+ client_id: data.clientId,
906
+ username: account.preferred_username,
907
+ }
908
+ }
909
+
910
+ protected async signUserinfo(userinfo: Userinfo): Promise<SignedJwt> {
911
+ const client = await this.clientManager.getClient(userinfo.client_id)
912
+ return this.signer.sign(
913
+ {
914
+ alg: client.metadata.userinfo_signed_response_alg,
915
+ typ: 'JWT',
916
+ },
917
+ userinfo,
918
+ )
919
+ }
920
+
921
+ protected override async authenticateToken(
922
+ tokenType: OAuthTokenType,
923
+ token: AccessToken,
924
+ dpopJkt: string | null,
925
+ verifyOptions?: VerifyTokenClaimsOptions,
926
+ ) {
927
+ if (isTokenId(token)) {
928
+ this.assertTokenTypeAllowed(tokenType, AccessTokenType.id)
929
+
930
+ return this.tokenManager.authenticateTokenId(
931
+ tokenType,
932
+ token,
933
+ dpopJkt,
934
+ verifyOptions,
935
+ )
936
+ }
937
+
938
+ return super.authenticateToken(tokenType, token, dpopJkt, verifyOptions)
939
+ }
940
+
941
+ /**
942
+ * @returns An http request handler that can be used with node's http server
943
+ * or as a middleware with express / connect.
944
+ */
945
+ public httpHandler<
946
+ T = void,
947
+ Req extends IncomingMessage = IncomingMessage,
948
+ Res extends ServerResponse = ServerResponse,
949
+ >(options?: RouterOptions<Req, Res>): Handler<T, Req, Res> {
950
+ const router = this.buildRouter<T, Req, Res>(options)
951
+ return router.buildHandler()
952
+ }
953
+
954
+ public buildRouter<
955
+ T = void,
956
+ Req extends IncomingMessage = IncomingMessage,
957
+ Res extends ServerResponse = ServerResponse,
958
+ >({
959
+ onError = process.env['NODE_ENV'] === 'development'
960
+ ? (req, res, err, msg): void =>
961
+ console.error(`OAuthProvider error (${msg}):`, err)
962
+ : undefined,
963
+ }: RouterOptions<Req, Res> = {}) {
964
+ const deviceManager = new DeviceManager(this.deviceStore)
965
+
966
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
967
+ const server = this
968
+ const issuerUrl = new URL(server.issuer)
969
+ const issuerOrigin = issuerUrl.origin
970
+ const router = new Router<T, Req, Res>(issuerUrl)
971
+
972
+ // Utils
973
+
974
+ const csrfCookie = (uri: RequestUri) => `csrf-${uri}`
975
+
976
+ /**
977
+ * Creates a middleware that will serve static JSON content.
978
+ */
979
+ const staticJson = (json: unknown): Middleware<void, Req, Res> =>
980
+ combineMiddlewares([
981
+ function (req, res, next) {
982
+ res.setHeader('Access-Control-Allow-Origin', '*')
983
+ res.setHeader('Cache-Control', 'max-age=300')
984
+ next()
985
+ },
986
+ staticJsonHandler(json),
987
+ ])
988
+
989
+ /**
990
+ * Wrap an OAuth endpoint in a middleware that will set the appropriate
991
+ * response headers and format the response as JSON.
992
+ */
993
+ const dynamicJson = <T, TReq extends Req, TRes extends Res, Json>(
994
+ buildJson: (this: T, req: TReq, res: TRes) => Json | Promise<Json>,
995
+ status?: number,
996
+ ): Handler<T, TReq, TRes> =>
997
+ async function (req, res) {
998
+ res.setHeader('Access-Control-Allow-Origin', '*')
999
+
1000
+ // https://www.rfc-editor.org/rfc/rfc6749.html#section-5.1
1001
+ res.setHeader('Cache-Control', 'no-store')
1002
+ res.setHeader('Pragma', 'no-cache')
1003
+
1004
+ // https://datatracker.ietf.org/doc/html/rfc9449#section-8.2
1005
+ const dpopNonce = server.nextDpopNonce()
1006
+ if (dpopNonce) {
1007
+ const name = 'DPoP-Nonce'
1008
+ res.setHeader(name, dpopNonce)
1009
+ res.appendHeader('Access-Control-Expose-Headers', name)
1010
+ }
1011
+
1012
+ try {
1013
+ const result = await buildJson.call(this, req, res)
1014
+ if (result !== undefined) writeJson(res, result, status)
1015
+ else if (!res.headersSent) res.writeHead(status ?? 204).end()
1016
+ } catch (err) {
1017
+ if (!res.headersSent) {
1018
+ if (err instanceof WWWAuthenticateError) {
1019
+ const name = 'WWW-Authenticate'
1020
+ res.setHeader(name, err.wwwAuthenticateHeader)
1021
+ res.appendHeader('Access-Control-Expose-Headers', name)
1022
+ }
1023
+
1024
+ writeJson(res, buildErrorPayload(err), buildErrorStatus(err))
1025
+ } else {
1026
+ res.destroy()
1027
+ }
1028
+
1029
+ // OAuthError are used to build expected responses, so we don't log
1030
+ // them as errors.
1031
+ if (!(err instanceof OAuthError) || err.statusCode >= 500) {
1032
+ await onError?.(req, res, err, 'Unexpected error')
1033
+ }
1034
+ }
1035
+ }
1036
+
1037
+ //- Public OAuth endpoints
1038
+
1039
+ /*
1040
+ * Although OpenID compatibility is not required to implement the Atproto
1041
+ * OAuth2 specification, we do support OIDC discovery in this
1042
+ * implementation as we believe this may:
1043
+ * 1) Make the implementation of Atproto clients easier (since lots of
1044
+ * libraries support OIDC discovery)
1045
+ * 2) Allow self hosted PDS' to not implement authentication themselves
1046
+ * but rely on a trusted Atproto actor to act as their OIDC providers.
1047
+ * By supporting OIDC in the current implementation, Bluesky's
1048
+ * Authorization Server server can be used as an OIDC provider for
1049
+ * these users.
1050
+ */
1051
+ router.get('/.well-known/openid-configuration', staticJson(server.metadata))
1052
+
1053
+ router.get(
1054
+ '/.well-known/oauth-authorization-server',
1055
+ staticJson(server.metadata),
1056
+ )
1057
+
1058
+ // CORS preflight
1059
+ router.options<{
1060
+ endpoint: 'jwks' | 'par' | 'token' | 'revoke' | 'introspect' | 'userinfo'
1061
+ }>(
1062
+ /^\/oauth\/(?<endpoint>jwks|par|token|revoke|introspect|userinfo)$/,
1063
+ function (req, res, _next) {
1064
+ res
1065
+ .writeHead(204, {
1066
+ 'Access-Control-Allow-Origin': req.headers['origin'] || '*',
1067
+ 'Access-Control-Allow-Methods':
1068
+ this.params.endpoint === 'jwks' ? 'GET' : 'POST',
1069
+ 'Access-Control-Allow-Headers': 'Content-Type,Authorization,DPoP',
1070
+ 'Access-Control-Max-Age': '86400', // 1 day
1071
+ })
1072
+ .end()
1073
+ },
1074
+ )
1075
+
1076
+ router.get('/oauth/jwks', staticJson(server.jwks))
1077
+
1078
+ router.post(
1079
+ '/oauth/par',
1080
+ dynamicJson(async function (req, _res) {
1081
+ const input = await validateRequestPayload(
1082
+ req,
1083
+ pushedAuthorizationRequestSchema,
1084
+ )
1085
+
1086
+ const dpopJkt = await server.checkDpopProof(
1087
+ req.headers['dpop'],
1088
+ req.method!,
1089
+ this.url,
1090
+ )
1091
+
1092
+ return server.pushedAuthorizationRequest(input, dpopJkt)
1093
+ }, 201),
1094
+ )
1095
+
1096
+ // https://datatracker.ietf.org/doc/html/rfc9126#section-2.3
1097
+ router.addRoute('*', '/oauth/par', (req, res) => {
1098
+ res.writeHead(405).end()
1099
+ })
1100
+
1101
+ router.post(
1102
+ '/oauth/token',
1103
+ dynamicJson(async function (req, _res) {
1104
+ const input = await validateRequestPayload(req, tokenRequestSchema)
1105
+
1106
+ const dpopJkt = await server.checkDpopProof(
1107
+ req.headers['dpop'],
1108
+ req.method!,
1109
+ this.url,
1110
+ )
1111
+
1112
+ return server.token(input, dpopJkt)
1113
+ }),
1114
+ )
1115
+
1116
+ router.post(
1117
+ '/oauth/revoke',
1118
+ dynamicJson(async function (req, res) {
1119
+ const input = await validateRequestPayload(req, revokeSchema)
1120
+
1121
+ try {
1122
+ await server.revoke(input)
1123
+ } catch (err) {
1124
+ onError?.(req, res, err, 'Failed to revoke token')
1125
+ }
1126
+ }),
1127
+ )
1128
+
1129
+ router.get(
1130
+ '/oauth/revoke',
1131
+ dynamicJson(async function (req, res) {
1132
+ validateFetchMode(req, res, ['navigate'])
1133
+ validateSameOrigin(req, res, issuerOrigin)
1134
+
1135
+ const query = Object.fromEntries(this.url.searchParams)
1136
+ const input = revokeSchema.parse(query, { path: ['query'] })
1137
+
1138
+ try {
1139
+ await server.revoke(input)
1140
+ } catch (err) {
1141
+ onError?.(req, res, err, 'Failed to revoke token')
1142
+ }
1143
+
1144
+ // Same as POST + redirect to callback URL
1145
+ // todo: generate JSONP response (if "callback" is provided)
1146
+
1147
+ throw new Error(
1148
+ 'You are successfully logged out. Redirect not implemented',
1149
+ )
1150
+ }),
1151
+ )
1152
+
1153
+ router.post(
1154
+ '/oauth/introspect',
1155
+ dynamicJson(async function (req, _res) {
1156
+ const input = await validateRequestPayload(req, introspectSchema)
1157
+ return server.introspect(input)
1158
+ }),
1159
+ )
1160
+
1161
+ const userinfoBodySchema = z.object({
1162
+ access_token: signedJwtSchema.optional(),
1163
+ })
1164
+
1165
+ router.addRoute(
1166
+ ['GET', 'POST'],
1167
+ '/oauth/userinfo',
1168
+ acceptMiddleware(
1169
+ async function (req, _res) {
1170
+ const body =
1171
+ req.method === 'POST'
1172
+ ? await validateRequestPayload(req, userinfoBodySchema)
1173
+ : null
1174
+
1175
+ if (body?.access_token && req.headers['authorization']) {
1176
+ throw new InvalidRequestError(
1177
+ 'access token must be provided in either the authorization header or the request body',
1178
+ )
1179
+ }
1180
+
1181
+ const auth = await server.authenticateRequest(
1182
+ req.method!,
1183
+ this.url,
1184
+ body?.access_token // Allow credentials to be parsed from body.
1185
+ ? {
1186
+ authorization: `Bearer ${body.access_token}`,
1187
+ dpop: undefined, // DPoP can only be used with headers
1188
+ }
1189
+ : req.headers,
1190
+ {
1191
+ scope: ['profile'],
1192
+ },
1193
+ )
1194
+
1195
+ const tokenInfo: TokenInfo =
1196
+ 'tokenInfo' in auth
1197
+ ? (auth.tokenInfo as TokenInfo)
1198
+ : await server.tokenManager.getTokenInfo(
1199
+ auth.tokenType,
1200
+ auth.tokenId,
1201
+ )
1202
+
1203
+ return server.userinfo(tokenInfo)
1204
+ },
1205
+ {
1206
+ '': 'application/json',
1207
+ 'application/json': dynamicJson(async function (_req, _res) {
1208
+ return this.data
1209
+ }),
1210
+ 'application/jwt': dynamicJson(async function (_req, res) {
1211
+ const jwt = await server.signUserinfo(this.data)
1212
+ res.writeHead(200, { 'Content-Type': 'application/jwt' }).end(jwt)
1213
+ return undefined
1214
+ }),
1215
+ },
1216
+ ),
1217
+ )
1218
+
1219
+ //- Private authorization endpoints
1220
+
1221
+ router.use(authorizeAssetsMiddleware())
1222
+
1223
+ router.get('/oauth/authorize', async function (req, res) {
1224
+ try {
1225
+ res.setHeader('Cache-Control', 'no-store')
1226
+
1227
+ validateFetchMode(req, res, ['navigate'])
1228
+ validateSameOrigin(req, res, issuerOrigin)
1229
+
1230
+ const query = Object.fromEntries(this.url.searchParams)
1231
+ const input = await authorizationRequestQuerySchema.parseAsync(query, {
1232
+ path: ['query'],
1233
+ })
1234
+
1235
+ const { deviceId } = await deviceManager.load(req, res)
1236
+ const data = await server.authorize(deviceId, input)
1237
+
1238
+ switch (true) {
1239
+ case 'redirect' in data: {
1240
+ return await sendAuthorizeRedirect(res, data)
1241
+ }
1242
+ case 'authorize' in data: {
1243
+ await setupCsrfToken(req, res, csrfCookie(data.authorize.uri))
1244
+ return await sendAuthorizePage(res, data, server.customization)
1245
+ }
1246
+ default: {
1247
+ // Should never happen
1248
+ throw new Error('Unexpected authorization result')
1249
+ }
1250
+ }
1251
+ } catch (err) {
1252
+ await onError?.(req, res, err, 'Failed to setup authorize')
1253
+
1254
+ if (!res.headersSent) {
1255
+ await sendErrorPage(res, err, server.customization)
1256
+ }
1257
+ }
1258
+ })
1259
+
1260
+ const signInPayloadSchema = z.object({
1261
+ csrf_token: z.string(),
1262
+ request_uri: requestUriSchema,
1263
+ client_id: clientIdSchema,
1264
+ credentials: z.object({
1265
+ username: z.string(),
1266
+ password: z.string(),
1267
+ remember: z.boolean().optional().default(false),
1268
+ }),
1269
+ })
1270
+
1271
+ router.post('/oauth/authorize/sign-in', async function (req, res) {
1272
+ validateFetchMode(req, res, ['same-origin'])
1273
+ validateSameOrigin(req, res, issuerOrigin)
1274
+
1275
+ const input = await validateRequestPayload(req, signInPayloadSchema)
1276
+
1277
+ validateReferer(req, res, {
1278
+ origin: issuerOrigin,
1279
+ pathname: '/oauth/authorize',
1280
+ })
1281
+ validateCsrfToken(
1282
+ req,
1283
+ res,
1284
+ input.csrf_token,
1285
+ csrfCookie(input.request_uri),
1286
+ )
1287
+
1288
+ const { deviceId } = await deviceManager.load(req, res)
1289
+
1290
+ const { account, info } = await server.signIn(deviceId, input.credentials)
1291
+
1292
+ // Prevent fixation attacks
1293
+ await deviceManager.rotate(req, res, deviceId)
1294
+
1295
+ return writeJson(res, {
1296
+ account,
1297
+ consentRequired: !info.authorizedClients.includes(input.client_id),
1298
+ })
1299
+ })
1300
+
1301
+ const acceptQuerySchema = z.object({
1302
+ csrf_token: z.string(),
1303
+ request_uri: requestUriSchema,
1304
+ client_id: clientIdSchema,
1305
+ account_sub: z.string(),
1306
+ })
1307
+
1308
+ router.get('/oauth/authorize/accept', async function (req, res) {
1309
+ try {
1310
+ res.setHeader('Cache-Control', 'no-store')
1311
+
1312
+ validateFetchMode(req, res, ['navigate'])
1313
+ validateSameOrigin(req, res, issuerOrigin)
1314
+
1315
+ const query = Object.fromEntries(this.url.searchParams)
1316
+ const input = await acceptQuerySchema.parseAsync(query, {
1317
+ path: ['query'],
1318
+ })
1319
+
1320
+ validateReferer(req, res, {
1321
+ origin: issuerOrigin,
1322
+ pathname: '/oauth/authorize',
1323
+ searchParams: [
1324
+ ['request_uri', input.request_uri],
1325
+ ['client_id', input.client_id],
1326
+ ],
1327
+ })
1328
+ validateCsrfToken(
1329
+ req,
1330
+ res,
1331
+ input.csrf_token,
1332
+ csrfCookie(input.request_uri),
1333
+ true,
1334
+ )
1335
+
1336
+ const { deviceId } = await deviceManager.load(req, res)
1337
+
1338
+ const data = await server.acceptRequest(
1339
+ deviceId,
1340
+ input.request_uri,
1341
+ input.client_id,
1342
+ input.account_sub,
1343
+ )
1344
+
1345
+ return await sendAuthorizeRedirect(res, data)
1346
+ } catch (err) {
1347
+ await onError?.(req, res, err, 'Failed to accept authorization request')
1348
+
1349
+ if (!res.headersSent) {
1350
+ await sendErrorPage(res, err, server.customization)
1351
+ }
1352
+ }
1353
+ })
1354
+
1355
+ const rejectQuerySchema = z.object({
1356
+ csrf_token: z.string(),
1357
+ request_uri: requestUriSchema,
1358
+ client_id: clientIdSchema,
1359
+ })
1360
+
1361
+ router.get('/oauth/authorize/reject', async function (req, res) {
1362
+ try {
1363
+ res.setHeader('Cache-Control', 'no-store')
1364
+
1365
+ validateFetchMode(req, res, ['navigate'])
1366
+ validateSameOrigin(req, res, issuerOrigin)
1367
+
1368
+ const query = Object.fromEntries(this.url.searchParams)
1369
+ const input = await rejectQuerySchema.parseAsync(query, {
1370
+ path: ['query'],
1371
+ })
1372
+
1373
+ validateReferer(req, res, {
1374
+ origin: issuerOrigin,
1375
+ pathname: '/oauth/authorize',
1376
+ searchParams: [
1377
+ ['request_uri', input.request_uri],
1378
+ ['client_id', input.client_id],
1379
+ ],
1380
+ })
1381
+ validateCsrfToken(
1382
+ req,
1383
+ res,
1384
+ input.csrf_token,
1385
+ csrfCookie(input.request_uri),
1386
+ true,
1387
+ )
1388
+
1389
+ const { deviceId } = await deviceManager.load(req, res)
1390
+
1391
+ const data = await server.rejectRequest(
1392
+ deviceId,
1393
+ input.request_uri,
1394
+ input.client_id,
1395
+ )
1396
+
1397
+ return await sendAuthorizeRedirect(res, data)
1398
+ } catch (err) {
1399
+ await onError?.(req, res, err, 'Failed to reject authorization request')
1400
+
1401
+ if (!res.headersSent) {
1402
+ await sendErrorPage(res, err, server.customization)
1403
+ }
1404
+ }
1405
+ })
1406
+
1407
+ return router
1408
+ }
1409
+ }