@atproto/oauth-provider 0.6.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/access-token/access-token-mode.d.ts +5 -0
  3. package/dist/access-token/access-token-mode.d.ts.map +1 -0
  4. package/dist/access-token/access-token-mode.js +9 -0
  5. package/dist/access-token/access-token-mode.js.map +1 -0
  6. package/dist/account/account-manager.d.ts +13 -7
  7. package/dist/account/account-manager.d.ts.map +1 -1
  8. package/dist/account/account-manager.js +69 -52
  9. package/dist/account/account-manager.js.map +1 -1
  10. package/dist/account/account-store.d.ts +88 -77
  11. package/dist/account/account-store.d.ts.map +1 -1
  12. package/dist/account/account-store.js +24 -73
  13. package/dist/account/account-store.js.map +1 -1
  14. package/dist/account/sign-in-data.d.ts +4 -13
  15. package/dist/account/sign-in-data.d.ts.map +1 -1
  16. package/dist/account/sign-in-data.js +9 -9
  17. package/dist/account/sign-in-data.js.map +1 -1
  18. package/dist/account/sign-up-input.d.ts +4 -5
  19. package/dist/account/sign-up-input.d.ts.map +1 -1
  20. package/dist/account/sign-up-input.js +13 -3
  21. package/dist/account/sign-up-input.js.map +1 -1
  22. package/dist/client/client-manager.d.ts +4 -1
  23. package/dist/client/client-manager.d.ts.map +1 -1
  24. package/dist/client/client-manager.js +13 -1
  25. package/dist/client/client-manager.js.map +1 -1
  26. package/dist/client/client-store.d.ts +1 -1
  27. package/dist/client/client-store.d.ts.map +1 -1
  28. package/dist/constants.d.ts +5 -1
  29. package/dist/constants.d.ts.map +1 -1
  30. package/dist/constants.js +6 -2
  31. package/dist/constants.js.map +1 -1
  32. package/dist/customization/branding.d.ts +54 -0
  33. package/dist/customization/branding.d.ts.map +1 -0
  34. package/dist/customization/branding.js +13 -0
  35. package/dist/customization/branding.js.map +1 -0
  36. package/dist/customization/build-customization-css.d.ts +3 -0
  37. package/dist/customization/build-customization-css.d.ts.map +1 -0
  38. package/dist/customization/build-customization-css.js +27 -0
  39. package/dist/customization/build-customization-css.js.map +1 -0
  40. package/dist/customization/build-customization-data.d.ts +4 -0
  41. package/dist/customization/build-customization-data.d.ts.map +1 -0
  42. package/dist/customization/build-customization-data.js +18 -0
  43. package/dist/customization/build-customization-data.js.map +1 -0
  44. package/dist/customization/colors.d.ts +7 -0
  45. package/dist/customization/colors.d.ts.map +1 -0
  46. package/dist/customization/colors.js +27 -0
  47. package/dist/customization/colors.js.map +1 -0
  48. package/dist/customization/customization.d.ts +129 -0
  49. package/dist/customization/customization.d.ts.map +1 -0
  50. package/dist/customization/customization.js +26 -0
  51. package/dist/customization/customization.js.map +1 -0
  52. package/dist/customization/links.d.ts +26 -0
  53. package/dist/customization/links.d.ts.map +1 -0
  54. package/dist/customization/links.js +12 -0
  55. package/dist/customization/links.js.map +1 -0
  56. package/dist/device/device-id.d.ts +1 -0
  57. package/dist/device/device-id.d.ts.map +1 -1
  58. package/dist/device/device-id.js +4 -0
  59. package/dist/device/device-id.js.map +1 -1
  60. package/dist/device/device-manager.d.ts +6 -36
  61. package/dist/device/device-manager.d.ts.map +1 -1
  62. package/dist/device/device-manager.js +49 -43
  63. package/dist/device/device-manager.js.map +1 -1
  64. package/dist/device/device-store.d.ts +1 -0
  65. package/dist/device/device-store.d.ts.map +1 -1
  66. package/dist/device/device-store.js.map +1 -1
  67. package/dist/dpop/dpop-manager.d.ts +3 -3
  68. package/dist/dpop/dpop-nonce.d.ts +3 -3
  69. package/dist/dpop/dpop-nonce.d.ts.map +1 -1
  70. package/dist/errors/access-denied-error.d.ts +4 -3
  71. package/dist/errors/access-denied-error.d.ts.map +1 -1
  72. package/dist/errors/access-denied-error.js +5 -6
  73. package/dist/errors/access-denied-error.js.map +1 -1
  74. package/dist/{output/build-error-payload.d.ts → errors/error-parser.d.ts} +1 -1
  75. package/dist/errors/error-parser.d.ts.map +1 -0
  76. package/dist/{output/build-error-payload.js → errors/error-parser.js} +2 -2
  77. package/dist/errors/error-parser.js.map +1 -0
  78. package/dist/errors/invalid-grant-error.d.ts +1 -0
  79. package/dist/errors/invalid-grant-error.d.ts.map +1 -1
  80. package/dist/errors/invalid-grant-error.js +5 -0
  81. package/dist/errors/invalid-grant-error.js.map +1 -1
  82. package/dist/errors/login-required-error.d.ts +1 -0
  83. package/dist/errors/login-required-error.d.ts.map +1 -1
  84. package/dist/errors/login-required-error.js +5 -0
  85. package/dist/errors/login-required-error.js.map +1 -1
  86. package/dist/index.d.ts +1 -0
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +1 -0
  89. package/dist/index.js.map +1 -1
  90. package/dist/lib/html/build-document.d.ts +2 -2
  91. package/dist/lib/html/build-document.d.ts.map +1 -1
  92. package/dist/lib/html/build-document.js +4 -0
  93. package/dist/lib/html/build-document.js.map +1 -1
  94. package/dist/lib/html/hydration-data.d.ts +4 -0
  95. package/dist/lib/html/hydration-data.d.ts.map +1 -0
  96. package/dist/{output/backend-data.js → lib/html/hydration-data.js} +8 -8
  97. package/dist/lib/html/hydration-data.js.map +1 -0
  98. package/dist/lib/html/tags.d.ts +1 -1
  99. package/dist/lib/html/tags.d.ts.map +1 -1
  100. package/dist/lib/html/tags.js +1 -1
  101. package/dist/lib/html/tags.js.map +1 -1
  102. package/dist/lib/http/accept.d.ts +2 -2
  103. package/dist/lib/http/accept.d.ts.map +1 -1
  104. package/dist/lib/http/accept.js +1 -1
  105. package/dist/lib/http/accept.js.map +1 -1
  106. package/dist/lib/http/context.d.ts +2 -4
  107. package/dist/lib/http/context.d.ts.map +1 -1
  108. package/dist/lib/http/context.js +29 -4
  109. package/dist/lib/http/context.js.map +1 -1
  110. package/dist/lib/http/headers.d.ts +3 -0
  111. package/dist/lib/http/headers.d.ts.map +1 -0
  112. package/dist/lib/http/headers.js +14 -0
  113. package/dist/lib/http/headers.js.map +1 -0
  114. package/dist/lib/http/index.d.ts +1 -0
  115. package/dist/lib/http/index.d.ts.map +1 -1
  116. package/dist/lib/http/index.js +1 -0
  117. package/dist/lib/http/index.js.map +1 -1
  118. package/dist/lib/http/middleware.d.ts +1 -1
  119. package/dist/lib/http/middleware.d.ts.map +1 -1
  120. package/dist/lib/http/middleware.js +8 -24
  121. package/dist/lib/http/middleware.js.map +1 -1
  122. package/dist/lib/http/parser.d.ts +3 -3
  123. package/dist/lib/http/parser.d.ts.map +1 -1
  124. package/dist/lib/http/request.d.ts +13 -9
  125. package/dist/lib/http/request.d.ts.map +1 -1
  126. package/dist/lib/http/request.js +27 -49
  127. package/dist/lib/http/request.js.map +1 -1
  128. package/dist/lib/http/response.d.ts +6 -2
  129. package/dist/lib/http/response.d.ts.map +1 -1
  130. package/dist/lib/http/response.js +31 -11
  131. package/dist/lib/http/response.js.map +1 -1
  132. package/dist/lib/http/route.d.ts +3 -3
  133. package/dist/lib/http/route.d.ts.map +1 -1
  134. package/dist/lib/http/route.js +1 -1
  135. package/dist/lib/http/route.js.map +1 -1
  136. package/dist/lib/http/router.d.ts +12 -11
  137. package/dist/lib/http/router.d.ts.map +1 -1
  138. package/dist/lib/http/router.js +26 -34
  139. package/dist/lib/http/router.js.map +1 -1
  140. package/dist/lib/http/security-headers.js +1 -1
  141. package/dist/lib/http/security-headers.js.map +1 -1
  142. package/dist/lib/http/stream.d.ts +3 -3
  143. package/dist/lib/http/stream.d.ts.map +1 -1
  144. package/dist/lib/http/types.d.ts +1 -1
  145. package/dist/lib/http/types.d.ts.map +1 -1
  146. package/dist/lib/send-web-page.d.ts +8 -0
  147. package/dist/lib/send-web-page.d.ts.map +1 -0
  148. package/dist/{output → lib}/send-web-page.js +9 -7
  149. package/dist/lib/send-web-page.js.map +1 -0
  150. package/dist/lib/util/authorization-header.d.ts.map +1 -1
  151. package/dist/lib/util/color.d.ts +32 -0
  152. package/dist/lib/util/color.d.ts.map +1 -0
  153. package/dist/lib/util/color.js +116 -0
  154. package/dist/lib/util/color.js.map +1 -0
  155. package/dist/lib/util/crypto.d.ts +1 -0
  156. package/dist/lib/util/crypto.d.ts.map +1 -1
  157. package/dist/lib/util/crypto.js +8 -3
  158. package/dist/lib/util/crypto.js.map +1 -1
  159. package/dist/lib/util/function.d.ts +1 -0
  160. package/dist/lib/util/function.d.ts.map +1 -1
  161. package/dist/lib/util/function.js +12 -0
  162. package/dist/lib/util/function.js.map +1 -1
  163. package/dist/lib/util/locale.d.ts +20 -0
  164. package/dist/lib/util/locale.d.ts.map +1 -0
  165. package/dist/lib/util/locale.js +14 -0
  166. package/dist/lib/util/locale.js.map +1 -0
  167. package/dist/lib/util/time.d.ts +1 -1
  168. package/dist/lib/util/time.d.ts.map +1 -1
  169. package/dist/lib/util/time.js +1 -1
  170. package/dist/lib/util/time.js.map +1 -1
  171. package/dist/lib/util/type.d.ts +22 -0
  172. package/dist/lib/util/type.d.ts.map +1 -1
  173. package/dist/lib/util/type.js.map +1 -1
  174. package/dist/lib/util/ui8.d.ts +4 -0
  175. package/dist/lib/util/ui8.d.ts.map +1 -0
  176. package/dist/lib/util/ui8.js +17 -0
  177. package/dist/lib/util/ui8.js.map +1 -0
  178. package/dist/lib/util/zod-error.d.ts +2 -0
  179. package/dist/lib/util/zod-error.d.ts.map +1 -0
  180. package/dist/lib/util/zod-error.js +16 -0
  181. package/dist/lib/util/zod-error.js.map +1 -0
  182. package/dist/oauth-errors.d.ts +22 -22
  183. package/dist/oauth-errors.d.ts.map +1 -1
  184. package/dist/oauth-errors.js +37 -45
  185. package/dist/oauth-errors.js.map +1 -1
  186. package/dist/oauth-hooks.d.ts +11 -23
  187. package/dist/oauth-hooks.d.ts.map +1 -1
  188. package/dist/oauth-hooks.js.map +1 -1
  189. package/dist/oauth-middleware.d.ts +12 -0
  190. package/dist/oauth-middleware.d.ts.map +1 -0
  191. package/dist/oauth-middleware.js +32 -0
  192. package/dist/oauth-middleware.js.map +1 -0
  193. package/dist/oauth-provider.d.ts +109 -113
  194. package/dist/oauth-provider.d.ts.map +1 -1
  195. package/dist/oauth-provider.js +124 -542
  196. package/dist/oauth-provider.js.map +1 -1
  197. package/dist/oauth-verifier.d.ts +7 -26
  198. package/dist/oauth-verifier.d.ts.map +1 -1
  199. package/dist/oauth-verifier.js +6 -16
  200. package/dist/oauth-verifier.js.map +1 -1
  201. package/dist/request/code.d.ts.map +1 -1
  202. package/dist/request/request-data.d.ts +2 -4
  203. package/dist/request/request-data.d.ts.map +1 -1
  204. package/dist/request/request-data.js.map +1 -1
  205. package/dist/request/request-manager.d.ts +4 -2
  206. package/dist/request/request-manager.d.ts.map +1 -1
  207. package/dist/request/request-manager.js +9 -8
  208. package/dist/request/request-manager.js.map +1 -1
  209. package/dist/request/request-store.d.ts +6 -0
  210. package/dist/request/request-store.d.ts.map +1 -1
  211. package/dist/request/request-store.js +3 -1
  212. package/dist/request/request-store.js.map +1 -1
  213. package/dist/result/authorization-redirect-parameters.d.ts +18 -0
  214. package/dist/result/authorization-redirect-parameters.d.ts.map +1 -0
  215. package/dist/result/authorization-redirect-parameters.js +3 -0
  216. package/dist/result/authorization-redirect-parameters.js.map +1 -0
  217. package/dist/result/authorization-result-authorize-page.d.ts +13 -0
  218. package/dist/result/authorization-result-authorize-page.d.ts.map +1 -0
  219. package/dist/result/authorization-result-authorize-page.js +3 -0
  220. package/dist/result/authorization-result-authorize-page.js.map +1 -0
  221. package/dist/result/authorization-result-redirect.d.ts +8 -0
  222. package/dist/result/authorization-result-redirect.d.ts.map +1 -0
  223. package/dist/result/authorization-result-redirect.js +3 -0
  224. package/dist/result/authorization-result-redirect.js.map +1 -0
  225. package/dist/router/assets/assets-manifest.d.ts +10 -0
  226. package/dist/router/assets/assets-manifest.d.ts.map +1 -0
  227. package/dist/router/assets/assets-manifest.js +77 -0
  228. package/dist/router/assets/assets-manifest.js.map +1 -0
  229. package/dist/router/assets/assets.d.ts +16 -0
  230. package/dist/router/assets/assets.d.ts.map +1 -0
  231. package/dist/router/assets/assets.js +43 -0
  232. package/dist/router/assets/assets.js.map +1 -0
  233. package/dist/router/assets/csrf.d.ts +4 -0
  234. package/dist/router/assets/csrf.d.ts.map +1 -0
  235. package/dist/router/assets/csrf.js +51 -0
  236. package/dist/router/assets/csrf.js.map +1 -0
  237. package/dist/router/assets/send-account-page.d.ts +7 -0
  238. package/dist/router/assets/send-account-page.d.ts.map +1 -0
  239. package/dist/router/assets/send-account-page.js +34 -0
  240. package/dist/router/assets/send-account-page.js.map +1 -0
  241. package/dist/router/assets/send-authorization-page.d.ts +5 -0
  242. package/dist/router/assets/send-authorization-page.d.ts.map +1 -0
  243. package/dist/router/assets/send-authorization-page.js +49 -0
  244. package/dist/router/assets/send-authorization-page.js.map +1 -0
  245. package/dist/router/assets/send-error-page.d.ts +4 -0
  246. package/dist/router/assets/send-error-page.d.ts.map +1 -0
  247. package/dist/router/assets/send-error-page.js +34 -0
  248. package/dist/router/assets/send-error-page.js.map +1 -0
  249. package/dist/router/create-account-page-middleware.d.ts +6 -0
  250. package/dist/router/create-account-page-middleware.d.ts.map +1 -0
  251. package/dist/router/create-account-page-middleware.js +39 -0
  252. package/dist/router/create-account-page-middleware.js.map +1 -0
  253. package/dist/router/create-api-middleware.d.ts +8 -0
  254. package/dist/router/create-api-middleware.d.ts.map +1 -0
  255. package/dist/router/create-api-middleware.js +501 -0
  256. package/dist/router/create-api-middleware.js.map +1 -0
  257. package/dist/router/create-authorization-page-middleware.d.ts +6 -0
  258. package/dist/router/create-authorization-page-middleware.d.ts.map +1 -0
  259. package/dist/router/create-authorization-page-middleware.js +104 -0
  260. package/dist/router/create-authorization-page-middleware.js.map +1 -0
  261. package/dist/router/create-oauth-middleware.d.ts +6 -0
  262. package/dist/router/create-oauth-middleware.d.ts.map +1 -0
  263. package/dist/router/create-oauth-middleware.js +142 -0
  264. package/dist/router/create-oauth-middleware.js.map +1 -0
  265. package/dist/router/error-handler.d.ts +3 -0
  266. package/dist/router/error-handler.d.ts.map +1 -0
  267. package/dist/{account/account.js → router/error-handler.js} +1 -1
  268. package/dist/router/error-handler.js.map +1 -0
  269. package/dist/router/middleware-options.d.ts +6 -0
  270. package/dist/router/middleware-options.d.ts.map +1 -0
  271. package/dist/router/middleware-options.js +3 -0
  272. package/dist/router/middleware-options.js.map +1 -0
  273. package/dist/router/send-redirect.d.ts +16 -0
  274. package/dist/router/send-redirect.d.ts.map +1 -0
  275. package/dist/{output/send-authorize-redirect.js → router/send-redirect.js} +40 -24
  276. package/dist/router/send-redirect.js.map +1 -0
  277. package/dist/{token/token-claims.d.ts → signer/api-token-payload.d.ts} +237 -232
  278. package/dist/signer/api-token-payload.d.ts.map +1 -0
  279. package/dist/signer/api-token-payload.js +17 -0
  280. package/dist/signer/api-token-payload.js.map +1 -0
  281. package/dist/signer/signed-token-payload.d.ts +164 -159
  282. package/dist/signer/signed-token-payload.d.ts.map +1 -1
  283. package/dist/signer/signed-token-payload.js +10 -16
  284. package/dist/signer/signed-token-payload.js.map +1 -1
  285. package/dist/signer/signer.d.ts +42 -11246
  286. package/dist/signer/signer.d.ts.map +1 -1
  287. package/dist/signer/signer.js +30 -15
  288. package/dist/signer/signer.js.map +1 -1
  289. package/dist/token/refresh-token.d.ts.map +1 -1
  290. package/dist/token/token-data.d.ts +1 -1
  291. package/dist/token/token-data.d.ts.map +1 -1
  292. package/dist/token/token-id.d.ts.map +1 -1
  293. package/dist/token/token-manager.d.ts +28 -26
  294. package/dist/token/token-manager.d.ts.map +1 -1
  295. package/dist/token/token-manager.js +138 -196
  296. package/dist/token/token-manager.js.map +1 -1
  297. package/dist/token/token-store.d.ts +4 -4
  298. package/dist/token/token-store.d.ts.map +1 -1
  299. package/dist/token/token-store.js +1 -0
  300. package/dist/token/token-store.js.map +1 -1
  301. package/dist/token/verify-token-claims.d.ts +3 -3
  302. package/dist/token/verify-token-claims.d.ts.map +1 -1
  303. package/dist/token/verify-token-claims.js +1 -1
  304. package/dist/token/verify-token-claims.js.map +1 -1
  305. package/dist/types/email-otp.d.ts +3 -0
  306. package/dist/types/email-otp.d.ts.map +1 -0
  307. package/dist/types/email-otp.js +6 -0
  308. package/dist/types/email-otp.js.map +1 -0
  309. package/dist/types/email.d.ts +3 -0
  310. package/dist/types/email.d.ts.map +1 -0
  311. package/dist/types/email.js +29 -0
  312. package/dist/types/email.js.map +1 -0
  313. package/dist/types/handle.d.ts +3 -0
  314. package/dist/types/handle.d.ts.map +1 -0
  315. package/dist/types/handle.js +22 -0
  316. package/dist/types/handle.js.map +1 -0
  317. package/dist/types/invite-code.d.ts +4 -0
  318. package/dist/types/invite-code.d.ts.map +1 -0
  319. package/dist/types/invite-code.js +6 -0
  320. package/dist/types/invite-code.js.map +1 -0
  321. package/dist/types/password.d.ts +4 -0
  322. package/dist/types/password.d.ts.map +1 -0
  323. package/dist/types/password.js +7 -0
  324. package/dist/types/password.js.map +1 -0
  325. package/package.json +10 -7
  326. package/src/access-token/access-token-mode.ts +4 -0
  327. package/src/account/account-manager.ts +105 -75
  328. package/src/account/account-store.ts +118 -114
  329. package/src/account/sign-in-data.ts +10 -10
  330. package/src/account/sign-up-input.ts +13 -4
  331. package/src/client/client-manager.ts +34 -2
  332. package/src/client/client-store.ts +1 -1
  333. package/src/constants.ts +6 -1
  334. package/src/customization/branding.ts +12 -0
  335. package/src/customization/build-customization-css.ts +30 -0
  336. package/src/customization/build-customization-data.ts +22 -0
  337. package/src/customization/colors.ts +30 -0
  338. package/src/customization/customization.ts +25 -0
  339. package/src/customization/links.ts +10 -0
  340. package/src/device/device-id.ts +5 -0
  341. package/src/device/device-manager.ts +76 -66
  342. package/src/device/device-store.ts +2 -0
  343. package/src/errors/access-denied-error.ts +24 -17
  344. package/src/{output/build-error-payload.ts → errors/error-parser.ts} +1 -1
  345. package/src/errors/invalid-grant-error.ts +5 -0
  346. package/src/errors/login-required-error.ts +10 -0
  347. package/src/index.ts +1 -0
  348. package/src/lib/html/build-document.ts +6 -4
  349. package/src/{output/backend-data.ts → lib/html/hydration-data.ts} +7 -5
  350. package/src/lib/html/tags.ts +2 -2
  351. package/src/lib/http/accept.ts +3 -3
  352. package/src/lib/http/context.ts +41 -10
  353. package/src/lib/http/headers.ts +15 -0
  354. package/src/lib/http/index.ts +1 -0
  355. package/src/lib/http/middleware.ts +8 -23
  356. package/src/lib/http/request.ts +40 -75
  357. package/src/lib/http/response.ts +39 -15
  358. package/src/lib/http/route.ts +8 -5
  359. package/src/lib/http/router.ts +40 -46
  360. package/src/lib/http/security-headers.ts +1 -1
  361. package/src/lib/http/types.ts +1 -6
  362. package/src/{output → lib}/send-web-page.ts +10 -9
  363. package/src/lib/util/color.ts +132 -0
  364. package/src/lib/util/crypto.ts +9 -4
  365. package/src/lib/util/function.ts +14 -0
  366. package/src/lib/util/locale.ts +18 -0
  367. package/src/lib/util/time.ts +3 -4
  368. package/src/lib/util/type.ts +24 -0
  369. package/src/lib/util/ui8.ts +14 -0
  370. package/src/lib/util/zod-error.ts +14 -0
  371. package/src/oauth-errors.ts +22 -22
  372. package/src/oauth-hooks.ts +11 -24
  373. package/src/oauth-middleware.ts +53 -0
  374. package/src/oauth-provider.ts +290 -1061
  375. package/src/oauth-verifier.ts +9 -55
  376. package/src/request/request-data.ts +5 -4
  377. package/src/request/request-manager.ts +11 -11
  378. package/src/request/request-store.ts +7 -0
  379. package/src/result/authorization-redirect-parameters.ts +24 -0
  380. package/src/result/authorization-result-authorize-page.ts +14 -0
  381. package/src/result/authorization-result-redirect.ts +8 -0
  382. package/src/router/assets/assets-manifest.ts +108 -0
  383. package/src/router/assets/assets.ts +54 -0
  384. package/src/router/assets/csrf.ts +63 -0
  385. package/src/router/assets/send-account-page.ts +43 -0
  386. package/src/router/assets/send-authorization-page.ts +62 -0
  387. package/src/router/assets/send-error-page.ts +42 -0
  388. package/src/router/create-account-page-middleware.ts +69 -0
  389. package/src/router/create-api-middleware.ts +814 -0
  390. package/src/router/create-authorization-page-middleware.ts +173 -0
  391. package/src/router/create-oauth-middleware.ts +247 -0
  392. package/src/router/error-handler.ts +6 -0
  393. package/src/router/middleware-options.ts +9 -0
  394. package/src/router/send-redirect.ts +142 -0
  395. package/src/signer/api-token-payload.ts +18 -0
  396. package/src/signer/signed-token-payload.ts +18 -28
  397. package/src/signer/signer.ts +49 -34
  398. package/src/token/token-data.ts +1 -1
  399. package/src/token/token-manager.ts +190 -239
  400. package/src/token/token-store.ts +6 -4
  401. package/src/token/verify-token-claims.ts +4 -4
  402. package/src/types/email-otp.ts +3 -0
  403. package/src/types/email.ts +26 -0
  404. package/src/types/handle.ts +18 -0
  405. package/src/types/invite-code.ts +4 -0
  406. package/src/types/password.ts +4 -0
  407. package/tsconfig.build.tsbuildinfo +1 -0
  408. package/tsconfig.json +1 -1
  409. package/dist/access-token/access-token-type.d.ts +0 -6
  410. package/dist/access-token/access-token-type.d.ts.map +0 -1
  411. package/dist/access-token/access-token-type.js +0 -10
  412. package/dist/access-token/access-token-type.js.map +0 -1
  413. package/dist/account/account.d.ts +0 -2
  414. package/dist/account/account.d.ts.map +0 -1
  415. package/dist/account/account.js.map +0 -1
  416. package/dist/assets/assets-middleware.d.ts +0 -5
  417. package/dist/assets/assets-middleware.d.ts.map +0 -1
  418. package/dist/assets/assets-middleware.js +0 -41
  419. package/dist/assets/assets-middleware.js.map +0 -1
  420. package/dist/lib/locale.d.ts +0 -15
  421. package/dist/lib/locale.d.ts.map +0 -1
  422. package/dist/lib/locale.js +0 -17
  423. package/dist/lib/locale.js.map +0 -1
  424. package/dist/output/backend-data.d.ts +0 -4
  425. package/dist/output/backend-data.d.ts.map +0 -1
  426. package/dist/output/backend-data.js.map +0 -1
  427. package/dist/output/build-authorize-data.d.ts +0 -29
  428. package/dist/output/build-authorize-data.d.ts.map +0 -1
  429. package/dist/output/build-authorize-data.js +0 -21
  430. package/dist/output/build-authorize-data.js.map +0 -1
  431. package/dist/output/build-customization-data.d.ts +0 -234
  432. package/dist/output/build-customization-data.d.ts.map +0 -1
  433. package/dist/output/build-customization-data.js +0 -174
  434. package/dist/output/build-customization-data.js.map +0 -1
  435. package/dist/output/build-error-data.d.ts +0 -3
  436. package/dist/output/build-error-data.d.ts.map +0 -1
  437. package/dist/output/build-error-data.js +0 -10
  438. package/dist/output/build-error-data.js.map +0 -1
  439. package/dist/output/build-error-payload.d.ts.map +0 -1
  440. package/dist/output/build-error-payload.js.map +0 -1
  441. package/dist/output/output-manager.d.ts +0 -28
  442. package/dist/output/output-manager.d.ts.map +0 -1
  443. package/dist/output/output-manager.js +0 -134
  444. package/dist/output/output-manager.js.map +0 -1
  445. package/dist/output/send-authorize-redirect.d.ts +0 -25
  446. package/dist/output/send-authorize-redirect.d.ts.map +0 -1
  447. package/dist/output/send-authorize-redirect.js.map +0 -1
  448. package/dist/output/send-web-page.d.ts +0 -8
  449. package/dist/output/send-web-page.d.ts.map +0 -1
  450. package/dist/output/send-web-page.js.map +0 -1
  451. package/dist/token/token-claims.d.ts.map +0 -1
  452. package/dist/token/token-claims.js +0 -27
  453. package/dist/token/token-claims.js.map +0 -1
  454. package/src/access-token/access-token-type.ts +0 -5
  455. package/src/account/account.ts +0 -1
  456. package/src/assets/assets-middleware.ts +0 -44
  457. package/src/lib/locale.ts +0 -21
  458. package/src/output/build-authorize-data.ts +0 -53
  459. package/src/output/build-customization-data.ts +0 -217
  460. package/src/output/build-error-data.ts +0 -8
  461. package/src/output/output-manager.ts +0 -188
  462. package/src/output/send-authorize-redirect.ts +0 -137
  463. package/src/token/token-claims.ts +0 -30
  464. package/tsconfig.backend.tsbuildinfo +0 -1
  465. /package/{tsconfig.backend.json → tsconfig.build.json} +0 -0
@@ -6,7 +6,6 @@ import {
6
6
  OAuthTokenType,
7
7
  oauthIssuerIdentifierSchema,
8
8
  } from '@atproto/oauth-types'
9
- import { AccessTokenType } from './access-token/access-token-type.js'
10
9
  import { DpopManager, DpopManagerOptions } from './dpop/dpop-manager.js'
11
10
  import { DpopNonce } from './dpop/dpop-nonce.js'
12
11
  import { InvalidDpopProofError } from './errors/invalid-dpop-proof-error.js'
@@ -40,24 +39,6 @@ export type OAuthVerifierOptions = Override<
40
39
  */
41
40
  keyset: Keyset | Iterable<Key | undefined | null | false>
42
41
 
43
- /**
44
- * If set to {@link AccessTokenType.jwt}, the provider will use JWTs for
45
- * access tokens. If set to {@link AccessTokenType.id}, the provider will
46
- * use tokenId as access tokens. If set to {@link AccessTokenType.auto},
47
- * JWTs will only be used if the audience is different from the issuer.
48
- * Defaults to {@link AccessTokenType.jwt}.
49
- *
50
- * Here is a comparison of the two types:
51
- *
52
- * - pro id: less CPU intensive (no crypto operations)
53
- * - pro id: less bandwidth (shorter tokens than jwt)
54
- * - pro id: token data is in sync with database (e.g. revocation)
55
- * - pro jwt: stateless: no I/O needed (no db lookups through token store)
56
- * - pro jwt: stateless: allows Resource Server to be on a different
57
- * host/server
58
- */
59
- accessTokenType?: AccessTokenType
60
-
61
42
  /**
62
43
  * A redis instance to use for replay protection. If not provided, replay
63
44
  * protection will use memory storage.
@@ -68,22 +49,16 @@ export type OAuthVerifierOptions = Override<
68
49
  }
69
50
  >
70
51
 
71
- export {
72
- AccessTokenType,
73
- DpopNonce,
74
- Keyset,
75
- type ReplayStore,
76
- type VerifyTokenClaimsOptions,
77
- }
52
+ export { DpopNonce, Key, Keyset }
53
+ export type { RedisOptions, ReplayStore, VerifyTokenClaimsOptions }
78
54
 
79
55
  export class OAuthVerifier {
80
56
  public readonly issuer: OAuthIssuerIdentifier
81
57
  public readonly keyset: Keyset
82
58
 
83
- protected readonly accessTokenType: AccessTokenType
84
- protected readonly dpopManager: DpopManager
85
- protected readonly replayManager: ReplayManager
86
- protected readonly signer: Signer
59
+ public readonly dpopManager: DpopManager
60
+ public readonly replayManager: ReplayManager
61
+ public readonly signer: Signer
87
62
 
88
63
  constructor({
89
64
  redis,
@@ -92,7 +67,6 @@ export class OAuthVerifier {
92
67
  replayStore = redis != null
93
68
  ? new ReplayStoreRedis({ redis })
94
69
  : new ReplayStoreMemory(),
95
- accessTokenType = AccessTokenType.jwt,
96
70
 
97
71
  ...rest
98
72
  }: OAuthVerifierOptions) {
@@ -101,7 +75,7 @@ export class OAuthVerifier {
101
75
  const issuerParsed = oauthIssuerIdentifierSchema.parse(issuer)
102
76
  const issuerUrl = new URL(issuerParsed)
103
77
 
104
- // TODO (?) support issuer with path
78
+ // @TODO (?) support issuer with path
105
79
  if (issuerUrl.pathname !== '/') {
106
80
  throw new TypeError(
107
81
  `"issuer" must be an URL with no path, search or hash (${issuerUrl})`,
@@ -111,7 +85,6 @@ export class OAuthVerifier {
111
85
  this.issuer = issuerParsed
112
86
  this.keyset = keyset instanceof Keyset ? keyset : new Keyset(keyset)
113
87
 
114
- this.accessTokenType = accessTokenType
115
88
  this.dpopManager = new DpopManager(dpopMgrOptions)
116
89
  this.replayManager = new ReplayManager(replayStore)
117
90
  this.signer = new Signer(this.issuer, this.keyset)
@@ -142,19 +115,7 @@ export class OAuthVerifier {
142
115
  return jkt
143
116
  }
144
117
 
145
- protected assertTokenTypeAllowed(
146
- tokenType: OAuthTokenType,
147
- accessTokenType: AccessTokenType,
148
- ) {
149
- if (
150
- this.accessTokenType !== AccessTokenType.auto &&
151
- this.accessTokenType !== accessTokenType
152
- ) {
153
- throw new InvalidTokenError(tokenType, `Invalid token type`)
154
- }
155
- }
156
-
157
- protected async authenticateToken(
118
+ protected async verifyToken(
158
119
  tokenType: OAuthTokenType,
159
120
  token: OAuthAccessToken,
160
121
  dpopJkt: string | null,
@@ -164,8 +125,6 @@ export class OAuthVerifier {
164
125
  throw new InvalidTokenError(tokenType, `Malformed token`)
165
126
  }
166
127
 
167
- this.assertTokenTypeAllowed(tokenType, AccessTokenType.jwt)
168
-
169
128
  const { payload } = await this.signer
170
129
  .verifyAccessToken(token)
171
130
  .catch((err) => {
@@ -190,7 +149,7 @@ export class OAuthVerifier {
190
149
  dpop?: unknown
191
150
  },
192
151
  verifyOptions?: VerifyTokenClaimsOptions,
193
- ): Promise<VerifyTokenClaimsResult> {
152
+ ) {
194
153
  const [tokenType, token] = parseAuthorizationHeader(headers.authorization)
195
154
  try {
196
155
  const dpopJkt = await this.checkDpopProof(
@@ -204,12 +163,7 @@ export class OAuthVerifier {
204
163
  throw new InvalidDpopProofError(`DPoP proof required`)
205
164
  }
206
165
 
207
- return await this.authenticateToken(
208
- tokenType,
209
- token,
210
- dpopJkt,
211
- verifyOptions,
212
- )
166
+ return await this.verifyToken(tokenType, token, dpopJkt, verifyOptions)
213
167
  } catch (err) {
214
168
  if (err instanceof UseDpopNonceError) throw err.toWwwAuthenticateError()
215
169
  if (err instanceof WWWAuthenticateError) throw err
@@ -2,6 +2,7 @@ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
2
  import { ClientAuth } from '../client/client-auth.js'
3
3
  import { ClientId } from '../client/client-id.js'
4
4
  import { DeviceId } from '../device/device-id.js'
5
+ import { NonNullableKeys } from '../lib/util/type.js'
5
6
  import { Sub } from '../oidc/sub.js'
6
7
  import { Code } from './code.js'
7
8
 
@@ -15,10 +16,10 @@ export type RequestData = {
15
16
  code: Code | null
16
17
  }
17
18
 
18
- export type RequestDataAuthorized = RequestData & {
19
- sub: Sub
20
- deviceId: DeviceId
21
- }
19
+ export type RequestDataAuthorized = NonNullableKeys<
20
+ RequestData,
21
+ 'sub' | 'deviceId'
22
+ >
22
23
 
23
24
  export const isRequestDataAuthorized = (
24
25
  data: RequestData,
@@ -1,9 +1,9 @@
1
+ import type { Account } from '@atproto/oauth-provider-api'
1
2
  import {
2
3
  CLIENT_ASSERTION_TYPE_JWT_BEARER,
3
4
  OAuthAuthorizationRequestParameters,
4
5
  OAuthAuthorizationServerMetadata,
5
6
  } from '@atproto/oauth-types'
6
- import { Account } from '../account/account.js'
7
7
  import { ClientAuth } from '../client/client-auth.js'
8
8
  import { ClientId } from '../client/client-id.js'
9
9
  import { Client } from '../client/client.js'
@@ -132,7 +132,7 @@ export class RequestManager {
132
132
  throw new AccessDeniedError(
133
133
  parameters,
134
134
  `Unsupported grant_type "authorization_code"`,
135
- 'unsupported_grant_type',
135
+ 'invalid_request',
136
136
  )
137
137
  }
138
138
 
@@ -377,9 +377,9 @@ export class RequestManager {
377
377
  deviceId: DeviceId,
378
378
  deviceMetadata: RequestMetadata,
379
379
  ): Promise<Code> {
380
- const id = decodeRequestUri(uri)
380
+ const requestId = decodeRequestUri(uri)
381
381
 
382
- const data = await this.store.readRequest(id)
382
+ const data = await this.store.readRequest(requestId)
383
383
  if (!data) throw new InvalidRequestError('Unknown request_uri')
384
384
 
385
385
  try {
@@ -409,7 +409,7 @@ export class RequestManager {
409
409
  const code = await generateCode()
410
410
 
411
411
  // Bind the request to the account, preventing it from being used again.
412
- await this.store.updateRequest(id, {
412
+ await this.store.updateRequest(requestId, {
413
413
  sub: account.sub,
414
414
  code,
415
415
  // Allow the client to exchange the code for a token within the next 60 seconds.
@@ -422,11 +422,12 @@ export class RequestManager {
422
422
  parameters: data.parameters,
423
423
  deviceId,
424
424
  deviceMetadata,
425
+ requestId,
425
426
  })
426
427
 
427
428
  return code
428
429
  } catch (err) {
429
- await this.store.deleteRequest(id)
430
+ await this.store.deleteRequest(requestId)
430
431
  throw err
431
432
  }
432
433
  }
@@ -439,13 +440,12 @@ export class RequestManager {
439
440
  client: Client,
440
441
  clientAuth: ClientAuth,
441
442
  code: Code,
442
- ): Promise<RequestDataAuthorized> {
443
+ ): Promise<RequestDataAuthorized & { requestUri: RequestUri }> {
443
444
  const result = await this.store.findRequestByCode(code)
444
445
  if (!result) throw new InvalidGrantError('Invalid code')
445
446
 
447
+ const { id, data } = result
446
448
  try {
447
- const { data } = result
448
-
449
449
  if (!isRequestDataAuthorized(data)) {
450
450
  // Should never happen: maybe the store implementation is faulty ?
451
451
  throw new Error('Unexpected request state')
@@ -478,10 +478,10 @@ export class RequestManager {
478
478
  }
479
479
  }
480
480
 
481
- return data
481
+ return { ...data, requestUri: encodeRequestUri(id) }
482
482
  } finally {
483
483
  // A "code" can only be used once
484
- await this.store.deleteRequest(result.id)
484
+ await this.store.deleteRequest(id)
485
485
  }
486
486
  }
487
487
 
@@ -1,3 +1,4 @@
1
+ import { InvalidGrantError } from '../errors/invalid-grant-error.js'
1
2
  import { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'
2
3
  import { Code } from './code.js'
3
4
  import { RequestData } from './request-data.js'
@@ -19,6 +20,8 @@ export type FoundRequestResult = {
19
20
  data: RequestData
20
21
  }
21
22
 
23
+ export { InvalidGrantError }
24
+
22
25
  export interface RequestStore {
23
26
  createRequest(id: RequestId, data: RequestData): Awaitable<void>
24
27
  /**
@@ -28,6 +31,10 @@ export interface RequestStore {
28
31
  readRequest(id: RequestId): Awaitable<RequestData | null>
29
32
  updateRequest(id: RequestId, data: UpdateRequestData): Awaitable<void>
30
33
  deleteRequest(id: RequestId): void | Awaitable<void>
34
+ /**
35
+ * @throws {InvalidGrantError} - When the request is not found or has expired
36
+ * (allows to provide an error message instead of returning `null`).
37
+ */
31
38
  findRequestByCode(code: Code): Awaitable<FoundRequestResult | null>
32
39
  }
33
40
 
@@ -0,0 +1,24 @@
1
+ import { OAuthTokenType } from '@atproto/oauth-types'
2
+ import { Code } from '../request/code.js'
3
+
4
+ /**
5
+ * @note `iss` and `state` will be added from the
6
+ * {@link AuthorizationResultRedirect} object.
7
+ */
8
+ export type AuthorizationRedirectParameters =
9
+ | {
10
+ // iss: string
11
+ // state?: string
12
+ code: Code
13
+ id_token?: string
14
+ access_token?: string
15
+ token_type?: OAuthTokenType
16
+ expires_in?: string
17
+ }
18
+ | {
19
+ // iss: string
20
+ // state?: string
21
+ error: string
22
+ error_description?: string
23
+ error_uri?: string
24
+ }
@@ -0,0 +1,14 @@
1
+ import type { ScopeDetail, Session } from '@atproto/oauth-provider-api'
2
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
3
+ import { Client } from '../client/client.js'
4
+ import { RequestUri } from '../request/request-uri.js'
5
+
6
+ export type AuthorizationResultAuthorizePage = {
7
+ issuer: string
8
+ client: Client
9
+ parameters: OAuthAuthorizationRequestParameters
10
+
11
+ uri: RequestUri
12
+ scopeDetails?: ScopeDetail[]
13
+ sessions: readonly Session[]
14
+ }
@@ -0,0 +1,8 @@
1
+ import { OAuthAuthorizationRequestParameters } from '@atproto/oauth-types'
2
+ import { AuthorizationRedirectParameters } from './authorization-redirect-parameters.js'
3
+
4
+ export type AuthorizationResultRedirect = {
5
+ issuer: string
6
+ parameters: OAuthAuthorizationRequestParameters
7
+ redirect: AuthorizationRedirectParameters
8
+ }
@@ -0,0 +1,108 @@
1
+ import { createReadStream } from 'node:fs'
2
+ import { join } from 'node:path'
3
+ import { Readable } from 'node:stream'
4
+ import type {
5
+ Manifest,
6
+ ManifestItem,
7
+ } from '@atproto-labs/rollup-plugin-bundle-manifest'
8
+ import { AssetRef } from '../../lib/html/build-document.js'
9
+ import {
10
+ Middleware,
11
+ validateFetchDest,
12
+ validateFetchSite,
13
+ writeStream,
14
+ } from '../../lib/http/index.js'
15
+ import { Simplify } from '../../lib/util/type.js'
16
+
17
+ type Asset = {
18
+ [T in ManifestItem['type']]: Simplify<
19
+ Omit<Extract<ManifestItem, { type: T }>, 'data'> & {
20
+ stream: () => Readable
21
+ }
22
+ >
23
+ }[ManifestItem['type']]
24
+
25
+ const ASSETS_URL_PREFIX = '/@atproto/oauth-provider/~assets/'
26
+
27
+ export function parseAssetsManifest(manifestPath: string) {
28
+ // Using `require` instead of `JSON.parse(readFileSync())` so that node's
29
+ // watch mode can pick up changes to the manifest file.
30
+
31
+ // eslint-disable-next-line
32
+ const manifest = require(manifestPath) as Manifest
33
+
34
+ const assets = new Map<string, Asset>(
35
+ Object.entries(manifest).map(([filename, { data, ...item }]) => {
36
+ const buffer = data ? Buffer.from(data, 'base64') : null
37
+ const filepath = join(manifestPath, '..', filename)
38
+ const stream = buffer
39
+ ? () => Readable.from(buffer)
40
+ : () => createReadStream(filepath)
41
+ return [filename, { ...item, stream }]
42
+ }),
43
+ )
44
+
45
+ const assetsMiddleware: Middleware = (req, res, next) => {
46
+ if (req.method !== 'GET' && req.method !== 'HEAD') return next()
47
+ if (!req.url?.startsWith(ASSETS_URL_PREFIX)) return next()
48
+
49
+ const filename = decodeURIComponent(req.url.slice(ASSETS_URL_PREFIX.length))
50
+ if (!filename) return next()
51
+
52
+ const asset = assets.get(filename)
53
+ if (!asset) return next()
54
+
55
+ try {
56
+ // Allow "null" (ie. no header) to allow loading assets outside of a
57
+ // fetch context (not from a web page).
58
+ validateFetchSite(req, [null, 'none', 'cross-site', 'same-origin'])
59
+ validateFetchDest(req, [null, 'document', 'style', 'script'])
60
+ } catch (err) {
61
+ return next(err)
62
+ }
63
+
64
+ if (req.headers['if-none-match'] === asset.sha256) {
65
+ return void res.writeHead(304).end()
66
+ }
67
+
68
+ res.setHeader('ETag', asset.sha256)
69
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable')
70
+
71
+ writeStream(res, asset.stream(), { contentType: asset.mime })
72
+ }
73
+
74
+ return {
75
+ getAssets,
76
+ assetsMiddleware,
77
+ }
78
+
79
+ function getAssets(entryName: string) {
80
+ const scripts = getScripts(entryName)
81
+ if (!scripts.length) return null
82
+ const styles = getStyles(entryName)
83
+ return { scripts, styles }
84
+ }
85
+
86
+ function getScripts(entryName: string) {
87
+ return Array.from(assets)
88
+ .filter(
89
+ ([, asset]) =>
90
+ asset.type === 'chunk' && asset.isEntry && asset.name === entryName,
91
+ )
92
+ .map(assetEntryUrl)
93
+ }
94
+
95
+ function getStyles(_entryName: string) {
96
+ return Array.from(assets)
97
+ .filter(([, asset]) => asset.mime === 'text/css')
98
+ .map(assetEntryUrl)
99
+ }
100
+ }
101
+
102
+ function assetEntryUrl([filename]: [string, Asset]): AssetRef {
103
+ return { url: assetUrl(filename) }
104
+ }
105
+
106
+ function assetUrl(filename: string) {
107
+ return `${ASSETS_URL_PREFIX}${encodeURIComponent(filename)}`
108
+ }
@@ -0,0 +1,54 @@
1
+ import type { HydrationData as FeHydrationData } from '@atproto/oauth-provider-frontend/hydration-data'
2
+ import type { HydrationData as UiHydrationData } from '@atproto/oauth-provider-ui/hydration-data'
3
+ import { CspConfig } from '../../lib/csp/index.js'
4
+ import { combineMiddlewares } from '../../lib/http/middleware.js'
5
+ import { Simplify } from '../../lib/util/type.js'
6
+ import { parseAssetsManifest } from './assets-manifest.js'
7
+
8
+ // If the "ui" and "frontend" packages are ever unified, this can be replaced
9
+ // with a single expression:
10
+ //
11
+ // const { getAssets, assetsMiddleware } = parseAssetsManifest(
12
+ // require.resolve('@atproto/oauth-provider-ui/bundle-manifest.json'),
13
+ // )
14
+
15
+ const ui = parseAssetsManifest(
16
+ require.resolve('@atproto/oauth-provider-ui/bundle-manifest.json'),
17
+ )
18
+ const fe = parseAssetsManifest(
19
+ require.resolve('@atproto/oauth-provider-frontend/bundle-manifest.json'),
20
+ )
21
+
22
+ export type HydrationData = Simplify<UiHydrationData & FeHydrationData>
23
+
24
+ export function getAssets(entryName: keyof HydrationData) {
25
+ const assetRef = ui.getAssets(entryName) || fe.getAssets(entryName)
26
+ if (assetRef) return assetRef
27
+
28
+ // Fool-proof. Should never happen.
29
+ throw new Error(`Entry "${entryName}" not found in assets`)
30
+ }
31
+
32
+ export const assetsMiddleware = combineMiddlewares([
33
+ ui.assetsMiddleware,
34
+ fe.assetsMiddleware,
35
+ ])
36
+
37
+ export const SPA_CSP: CspConfig = {
38
+ // API calls are made to the same origin
39
+ 'connect-src': ["'self'"],
40
+ // Allow loading of PDS logo & User avatars
41
+ 'img-src': ['data:', 'https:'],
42
+ // Prevent embedding in iframes
43
+ 'frame-ancestors': ["'none'"],
44
+ }
45
+
46
+ /**
47
+ * @see {@link https://docs.hcaptcha.com/#content-security-policy-settings}
48
+ */
49
+ export const HCAPTCHA_CSP: CspConfig = {
50
+ 'script-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
51
+ 'frame-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
52
+ 'style-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
53
+ 'connect-src': ['https://hcaptcha.com', 'https://*.hcaptcha.com'],
54
+ }
@@ -0,0 +1,63 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+ import createHttpError from 'http-errors'
3
+ import { CSRF_COOKIE_NAME, CSRF_HEADER_NAME } from '@atproto/oauth-provider-api'
4
+ import {
5
+ CookieSerializeOptions,
6
+ parseHttpCookies,
7
+ setCookie,
8
+ } from '../../lib/http/index.js'
9
+ import { randomHexId } from '../../lib/util/crypto.js'
10
+
11
+ const TOKEN_BYTE_LENGTH = 12
12
+ const TOKEN_LENGTH = TOKEN_BYTE_LENGTH * 2 // 2 hex chars per byte
13
+
14
+ // @NOTE Cookie based CSRF protection is redundant with session cookies using
15
+ // `SameSite` and could probably be removed in the future.
16
+ const CSRF_COOKIE_OPTIONS: Readonly<CookieSerializeOptions> = {
17
+ expires: undefined, // "session" cookie
18
+ secure: true,
19
+ httpOnly: false, // Need to be accessible from JavaScript
20
+ sameSite: 'lax',
21
+ path: `/`,
22
+ }
23
+
24
+ export async function setupCsrfToken(
25
+ req: IncomingMessage,
26
+ res: ServerResponse,
27
+ ): Promise<string> {
28
+ const token = getCookieCsrf(req) || (await randomHexId(TOKEN_BYTE_LENGTH))
29
+
30
+ // Refresh cookie (See Chrome's "Lax+POST" behavior)
31
+ setCookie(res, CSRF_COOKIE_NAME, token, CSRF_COOKIE_OPTIONS)
32
+
33
+ return token
34
+ }
35
+
36
+ export async function validateCsrfToken(
37
+ req: IncomingMessage,
38
+ res: ServerResponse,
39
+ ) {
40
+ const cookieValue = await setupCsrfToken(req, res)
41
+ const headerValue = getHeadersCsrf(req)
42
+
43
+ if (cookieValue !== headerValue) {
44
+ throw createHttpError(400, `CSRF mismatch`)
45
+ }
46
+ }
47
+
48
+ function getCookieCsrf(req: IncomingMessage) {
49
+ const cookies = parseHttpCookies(req)
50
+ const cookieValue = cookies[CSRF_COOKIE_NAME]
51
+ if (cookieValue?.length === TOKEN_LENGTH) {
52
+ return cookieValue
53
+ }
54
+ return undefined
55
+ }
56
+
57
+ function getHeadersCsrf(req: IncomingMessage) {
58
+ const headerValue = req.headers[CSRF_HEADER_NAME]
59
+ if (typeof headerValue === 'string' && headerValue.length === TOKEN_LENGTH) {
60
+ return headerValue
61
+ }
62
+ return undefined
63
+ }
@@ -0,0 +1,43 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+ import type { ActiveDeviceSession } from '@atproto/oauth-provider-api'
3
+ import { buildCustomizationCss } from '../../customization/build-customization-css.js'
4
+ import { buildCustomizationData } from '../../customization/build-customization-data.js'
5
+ import { Customization } from '../../customization/customization.js'
6
+ import { declareHydrationData } from '../../lib/html/hydration-data.js'
7
+ import { cssCode } from '../../lib/html/index.js'
8
+ import { html } from '../../lib/html/tags.js'
9
+ import { CrossOriginEmbedderPolicy } from '../../lib/http/security-headers.js'
10
+ import { sendWebPage } from '../../lib/send-web-page.js'
11
+ import { HydrationData, SPA_CSP, getAssets } from './assets.js'
12
+ import { setupCsrfToken } from './csrf.js'
13
+
14
+ export function sendAccountPageFactory(customization: Customization) {
15
+ // Pre-computed options:
16
+ const customizationData = buildCustomizationData(customization)
17
+ const customizationCss = cssCode(buildCustomizationCss(customization))
18
+ const { scripts, styles } = getAssets('account-page')
19
+
20
+ return async function sendAccountPage(
21
+ req: IncomingMessage,
22
+ res: ServerResponse,
23
+ data: {
24
+ deviceSessions: readonly ActiveDeviceSession[]
25
+ },
26
+ ): Promise<void> {
27
+ await setupCsrfToken(req, res)
28
+
29
+ const script = declareHydrationData<HydrationData['account-page']>({
30
+ __customizationData: customizationData,
31
+ __deviceSessions: data.deviceSessions,
32
+ })
33
+
34
+ return sendWebPage(res, {
35
+ meta: [{ name: 'robots', content: 'noindex' }],
36
+ body: html`<div id="root"></div>`,
37
+ csp: SPA_CSP,
38
+ coep: CrossOriginEmbedderPolicy.credentialless,
39
+ scripts: [script, ...scripts],
40
+ styles: [...styles, customizationCss],
41
+ })
42
+ }
43
+ }
@@ -0,0 +1,62 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+ import { buildCustomizationCss } from '../../customization/build-customization-css.js'
3
+ import { buildCustomizationData } from '../../customization/build-customization-data.js'
4
+ import { Customization } from '../../customization/customization.js'
5
+ import { mergeCsp } from '../../lib/csp/index.js'
6
+ import { declareHydrationData } from '../../lib/html/hydration-data.js'
7
+ import { cssCode, html } from '../../lib/html/index.js'
8
+ import { CrossOriginEmbedderPolicy } from '../../lib/http/security-headers.js'
9
+ import { sendWebPage } from '../../lib/send-web-page.js'
10
+ import { AuthorizationResultAuthorizePage } from '../../result/authorization-result-authorize-page.js'
11
+ import { HCAPTCHA_CSP, HydrationData, SPA_CSP, getAssets } from './assets.js'
12
+ import { setupCsrfToken } from './csrf.js'
13
+
14
+ export function sendAuthorizePageFactory(customization: Customization) {
15
+ // Pre-computed options:
16
+ const customizationData = buildCustomizationData(customization)
17
+ const customizationCss = cssCode(buildCustomizationCss(customization))
18
+ const { scripts, styles } = getAssets('authorization-page')
19
+ const csp = mergeCsp(
20
+ SPA_CSP,
21
+ customization?.hcaptcha ? HCAPTCHA_CSP : undefined,
22
+ )
23
+ const coep = customization?.hcaptcha
24
+ ? // https://github.com/hCaptcha/react-hcaptcha/issues/259
25
+ // @TODO Remove the use of `unsafeNone` once the issue above is resolved
26
+ CrossOriginEmbedderPolicy.unsafeNone
27
+ : CrossOriginEmbedderPolicy.credentialless
28
+
29
+ return async function sendAuthorizePage(
30
+ req: IncomingMessage,
31
+ res: ServerResponse,
32
+ data: AuthorizationResultAuthorizePage,
33
+ ): Promise<void> {
34
+ await setupCsrfToken(req, res)
35
+
36
+ const script = declareHydrationData<HydrationData['authorization-page']>({
37
+ __customizationData: customizationData,
38
+ __authorizeData: {
39
+ requestUri: data.uri,
40
+
41
+ clientId: data.client.id,
42
+ clientMetadata: data.client.metadata,
43
+ clientTrusted: data.client.info.isTrusted,
44
+
45
+ scopeDetails: data.scopeDetails,
46
+
47
+ uiLocales: data.parameters.ui_locales,
48
+ loginHint: data.parameters.login_hint,
49
+ },
50
+ __sessions: data.sessions,
51
+ })
52
+
53
+ return sendWebPage(res, {
54
+ meta: [{ name: 'robots', content: 'noindex' }],
55
+ body: html`<div id="root"></div>`,
56
+ csp,
57
+ coep,
58
+ scripts: [script, ...scripts],
59
+ styles: [...styles, customizationCss],
60
+ })
61
+ }
62
+ }