@atproto/oauth-provider 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+ }