fedipod-server 0.29.0 → 0.31.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 (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,123 +0,0 @@
1
- // fedi-login.mjs — sign a person in at their own Solid pod from their
2
- // Fediverse address, and bring them back to where they were.
3
- //
4
- // import { fediLogin } from './fedi-login.mjs';
5
- // const login = fediLogin({ dbName: 'my-app-oidc', clientName: 'My app' });
6
- //
7
- // await login.login('@mei@fedipod.net'); // leaves for the pod's login page
8
- // await login.resume(); // on every page load: finishes a
9
- // // login in progress and returns them
10
- // const s = await login.getSession(); // null, or { webId, issuer, fetch, signOut }
11
- //
12
- // How an address becomes a login page. WebFinger at the address's host names
13
- // the actor and its aliases. An origin among those that answers
14
- // /.well-known/openid-configuration is the login provider, or says which is
15
- // (a pod host redirects there). Failing that, the WebID — the URL typed, or
16
- // the ones the actor names as its own — is read as JSON-LD and its
17
- // solid:oidcIssuer is the provider. A Mastodon address has neither, and the
18
- // person is told so in words.
19
- //
20
- // No imports but the session engine beside it, no Node built-ins, nothing
21
- // named after the application that uses it.
22
- import { solidOidcSession } from './oidc-session.mjs';
23
-
24
- const OIDC_ISSUER = 'http://www.w3.org/ns/solid/terms#oidcIssuer';
25
-
26
- /** `@mei@host`, `mei@host`, or a WebID or actor URL. Null when it is neither. */
27
- export function parseAddress(input) {
28
- const t = String(input || '').trim();
29
- if (/^https?:\/\//u.test(t)) { try { return { url: new URL(t).href, host: new URL(t).host }; } catch { return null; } }
30
- const m = /^@?([^@\s/]+)@([^@\s/]+)$/u.exec(t);
31
- if (!m) return null;
32
- const name = m[1]; const host = m[2].toLowerCase();
33
- return { name, host, at: `@${name}@${host}` };
34
- }
35
-
36
- export function fediLogin({ dbName, clientName, redirectUri = null, fetch: f = globalThis.fetch?.bind(globalThis) } = {}) {
37
- const oidc = solidOidcSession({ dbName, clientName });
38
- const get = (url, accept) => f(url, { headers: { accept }, redirect: 'follow' });
39
- const json = (url, accept) => get(url, accept).then((r) => (r.ok ? r.json() : null)).catch(() => null);
40
- const here = () => (globalThis.location ? location.origin + location.pathname : null);
41
-
42
- const discovery = (base) => json(`${String(base).replace(/\/+$/, '')}/.well-known/openid-configuration`, 'application/json');
43
- const isProvider = (cfg) => !!(cfg?.issuer && cfg.authorization_endpoint);
44
- const origin = (u) => { try { return new URL(u).origin; } catch { return null; } };
45
-
46
- // The issuer a WebID document names, read as the expanded JSON-LD a pod answers.
47
- const issuerOfWebId = async (webId) => {
48
- const doc = await json(webId, 'application/ld+json');
49
- const nodes = Array.isArray(doc) ? doc : (doc?.['@graph'] || (doc ? [doc] : []));
50
- const me = nodes.find((n) => n?.['@id'] === webId && n[OIDC_ISSUER]) || nodes.find((n) => n?.[OIDC_ISSUER]);
51
- const v = [].concat(me?.[OIDC_ISSUER] || [])[0];
52
- return typeof v === 'string' ? v : (v?.['@id'] || null);
53
- };
54
-
55
- /** Where an address signs in: { address, actor, origin, issuer, webId? }.
56
- * Throws, in plain words, when the host does not know the address or
57
- * nothing about it names a login provider. */
58
- async function resolve(address) {
59
- const a = parseAddress(address);
60
- if (!a) throw new Error('an address looks like @you@your.server');
61
- let actor = null;
62
- const urls = [];
63
- if (a.url) urls.push(a.url);
64
- else {
65
- const r = await get(`https://${a.host}/.well-known/webfinger?resource=${encodeURIComponent(`acct:${a.name}@${a.host}`)}`,
66
- 'application/jrd+json, application/json').catch(() => null);
67
- if (!r?.ok) throw new Error(`${a.host} does not know ${a.at}`);
68
- const jrd = await r.json().catch(() => null);
69
- const self = (jrd?.links || []).find((l) => l.rel === 'self' && /activity\+json|ld\+json/u.test(l.type || ''));
70
- actor = self?.href || null;
71
- // The aliases first: at a Gateway they name the actor on the pod itself,
72
- // and the pod is where the login is.
73
- for (const u of [...(jrd?.aliases || []), ...(actor ? [actor] : [])]) if (typeof u === 'string') urls.push(u);
74
- if (!urls.length) throw new Error(`${a.host} did not say where ${a.at} lives`);
75
- }
76
- const found = (extra) => ({ address: a.at || a.url, actor, ...extra });
77
-
78
- // 1. An origin that is the login provider, or says which is.
79
- for (const o of [...new Set(urls.map(origin).filter(Boolean))]) {
80
- const cfg = await discovery(o);
81
- if (isProvider(cfg)) return found({ origin: o, issuer: cfg.issuer });
82
- }
83
- // 2. The WebID: the URL typed, or the ones the actor names as its own.
84
- const webIds = a.url ? [a.url] : [];
85
- if (actor) {
86
- const doc = await json(actor, 'application/activity+json');
87
- for (const u of [].concat(doc?.alsoKnownAs || [])) if (typeof u === 'string') webIds.push(u);
88
- }
89
- for (const webId of webIds) {
90
- const issuer = await issuerOfWebId(webId);
91
- if (issuer && isProvider(await discovery(issuer))) return found({ origin: origin(webId), issuer, webId });
92
- }
93
- throw new Error(`${a.host} is not a Solid pod, so there is no pod login to send you to`);
94
- }
95
-
96
- /** Everything `login` does short of leaving the page: returns the found pod
97
- * and the `authorizationUrl` to send the person to. */
98
- async function startLogin(address, { returnTo = globalThis.location?.href || null, redirectUri: ru = redirectUri || here() } = {}) {
99
- const found = await resolve(address);
100
- const { authorizationUrl } = await oidc.beginLogin({ issuer: found.issuer, redirectUri: ru, returnTo });
101
- return { ...found, authorizationUrl, returnTo };
102
- }
103
-
104
- /** Send the person to their pod's login page. They come back to `redirectUri`
105
- * (this page, unless set) and `resume` there returns them to `returnTo`
106
- * (where they were, unless set). */
107
- async function login(address, opts) {
108
- const s = await startLogin(address, opts);
109
- location.href = s.authorizationUrl;
110
- return s;
111
- }
112
-
113
- /** Call on every page load. Finishes a login the person is coming back from
114
- * and returns them to where they were; null when no login is in progress. */
115
- async function resume({ currentUrl = globalThis.location?.href, go = (u) => location.replace(u) } = {}) {
116
- const session = await oidc.completeLogin({ currentUrl });
117
- if (!session) return null;
118
- if (session.returnTo && session.returnTo !== currentUrl) go(session.returnTo);
119
- return session;
120
- }
121
-
122
- return { resolve, startLogin, login, resume, getSession: oidc.getSession, signOut: oidc.signOut };
123
- }
@@ -1,233 +0,0 @@
1
- // oidc-session.mjs — Solid-OIDC login for a browser page, with a secure,
2
- // durable, worker-visible session.
3
- //
4
- // The redirect handshake (authorization-code + PKCE) runs in the page. The
5
- // session it yields — a DPoP keypair whose private half is NON-EXTRACTABLE, plus
6
- // the refresh token, the token endpoint and the WebID — is kept in IndexedDB.
7
- // IndexedDB survives a browser restart and is readable by a service worker,
8
- // and a non-extractable key can sign but never be read out, even by injected
9
- // script.
10
- //
11
- // import { solidOidcSession } from 'solid-oidc-session/oidc-session.mjs';
12
- // const oidc = solidOidcSession({ dbName: 'my-app-oidc', clientName: 'My app' });
13
- //
14
- // Page: const { authorizationUrl } = await oidc.beginLogin({ issuer, redirectUri });
15
- // location.href = authorizationUrl; // ... redirect ...
16
- // await oidc.completeLogin({ currentUrl: location.href }); // on return
17
- // Anywhere (page or worker, SAME dbName):
18
- // const session = await oidc.getSession(); // { webId, issuer, fetch, refresh, signOut } | null
19
- //
20
- // No imports, no Node built-ins, nothing named after the application that
21
- // uses it: IndexedDB, WebCrypto and fetch are all it needs.
22
-
23
- const STORE = 'session';
24
- const b64u = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
25
- const enc = (o) => b64u(new TextEncoder().encode(JSON.stringify(o)));
26
- const sha256 = (s) => crypto.subtle.digest('SHA-256', new TextEncoder().encode(s));
27
- const rand = (n = 32) => b64u(crypto.getRandomValues(new Uint8Array(n)));
28
-
29
- async function dpopKey() { return crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']); }
30
- async function publicJwk(pair) { const j = await crypto.subtle.exportKey('jwk', pair.publicKey); return { kty: j.kty, crv: j.crv, x: j.x, y: j.y }; }
31
- async function dpopProof(pair, jwk, htm, htu, ath) {
32
- const header = { typ: 'dpop+jwt', alg: 'ES256', jwk };
33
- const payload = { htm, htu: htu.split('#')[0], jti: crypto.randomUUID(), iat: Math.floor(Date.now() / 1000), ...(ath ? { ath } : {}) };
34
- const data = `${enc(header)}.${enc(payload)}`;
35
- const sig = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, pair.privateKey, new TextEncoder().encode(data));
36
- return `${data}.${b64u(sig)}`;
37
- }
38
- const jwtPayload = (jwt) => { try { return JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(jwt.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')), (c) => c.charCodeAt(0)))); } catch { return {}; } };
39
-
40
- async function discover(issuer) {
41
- const res = await fetch(`${issuer.replace(/\/+$/, '')}/.well-known/openid-configuration`, { headers: { accept: 'application/json' } });
42
- if (!res.ok) throw new Error(`OIDC discovery failed at ${issuer} (HTTP ${res.status})`);
43
- return res.json();
44
- }
45
- async function registerClient(cfg, redirectUri, clientName) {
46
- const res = await fetch(cfg.registration_endpoint, {
47
- method: 'POST', headers: { 'content-type': 'application/json' },
48
- body: JSON.stringify({ client_name: clientName, redirect_uris: [redirectUri], token_endpoint_auth_method: 'none',
49
- grant_types: ['authorization_code', 'refresh_token'], response_types: ['code'], application_type: 'web', scope: 'openid webid offline_access' }),
50
- });
51
- if (!res.ok) throw new Error(`client registration failed (HTTP ${res.status})`);
52
- return (await res.json()).client_id;
53
- }
54
-
55
- /**
56
- * One session store. `dbName` is the IndexedDB database the session lives in:
57
- * a page and its service worker must pass the same name to see the same
58
- * sign-in, and two apps on one origin pass different names to keep theirs
59
- * apart. `clientName` is what the pod's login screen shows the person.
60
- */
61
- export function solidOidcSession({ dbName = 'solid-oidc-session', clientName = 'Solid app' } = {}) {
62
- function idb() {
63
- return new Promise((res, rej) => {
64
- const r = indexedDB.open(dbName, 1);
65
- r.onupgradeneeded = () => r.result.createObjectStore(STORE);
66
- r.onsuccess = () => res(r.result);
67
- r.onerror = () => rej(r.error);
68
- });
69
- }
70
- async function idbGet(key) {
71
- const db = await idb();
72
- return new Promise((res, rej) => { const t = db.transaction(STORE).objectStore(STORE).get(key); t.onsuccess = () => res(t.result); t.onerror = () => rej(t.error); });
73
- }
74
- async function idbPut(key, val) {
75
- const db = await idb();
76
- return new Promise((res, rej) => { const t = db.transaction(STORE, 'readwrite').objectStore(STORE).put(val, key); t.onsuccess = () => res(); t.onerror = () => rej(t.error); });
77
- }
78
- async function idbDel(key) {
79
- const db = await idb();
80
- return new Promise((res, rej) => { const t = db.transaction(STORE, 'readwrite').objectStore(STORE).delete(key); t.onsuccess = () => res(); t.onerror = () => rej(t.error); });
81
- }
82
-
83
- /** Begin login: prepare PKCE + a non-extractable DPoP key, stash the pending
84
- * state, and return the authorization URL for the caller to navigate to. */
85
- async function beginLogin({ issuer, redirectUri, clientName: name = clientName, returnTo = null }) {
86
- const cfg = await discover(issuer);
87
- const client_id = await registerClient(cfg, redirectUri, name);
88
- const pair = await dpopKey();
89
- const verifier = rand(48);
90
- const challenge = b64u(await sha256(verifier));
91
- const state = rand(16);
92
- await idbPut('pending', { issuer, client_id, redirectUri, verifier, state, pair, returnTo,
93
- tokenEndpoint: cfg.token_endpoint, authorizationEndpoint: cfg.authorization_endpoint,
94
- revocationEndpoint: cfg.revocation_endpoint || null });
95
- const url = new URL(cfg.authorization_endpoint);
96
- for (const [k, v] of Object.entries({ client_id, redirect_uri: redirectUri, response_type: 'code',
97
- scope: 'openid webid offline_access', code_challenge: challenge, code_challenge_method: 'S256', state, prompt: 'consent' })) url.searchParams.set(k, v);
98
- return { authorizationUrl: url.href };
99
- }
100
-
101
- /** Complete login from the redirect back. Exchanges the code (DPoP + PKCE),
102
- * stores the durable session, and returns it. Returns null if there is no code. */
103
- async function completeLogin({ currentUrl }) {
104
- const u = new URL(currentUrl);
105
- const code = u.searchParams.get('code');
106
- const state = u.searchParams.get('state');
107
- if (!code) return null;
108
- const p = await idbGet('pending');
109
- if (!p || p.state !== state) throw new Error('login state mismatch');
110
- const jwk = await publicJwk(p.pair);
111
- const res = await fetch(p.tokenEndpoint, {
112
- method: 'POST',
113
- headers: { 'content-type': 'application/x-www-form-urlencoded', dpop: await dpopProof(p.pair, jwk, 'POST', p.tokenEndpoint) },
114
- body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: p.redirectUri, client_id: p.client_id, code_verifier: p.verifier }),
115
- });
116
- const tok = await res.json().catch(() => ({}));
117
- if (!res.ok || !tok.access_token) throw new Error(`token exchange failed (HTTP ${res.status}): ${tok.error || ''}`);
118
- const webId = jwtPayload(tok.access_token).webid || jwtPayload(tok.id_token || '').webid || null;
119
- const session = { issuer: p.issuer, client_id: p.client_id, tokenEndpoint: p.tokenEndpoint, pair: p.pair,
120
- // Where to hand the refresh token back at sign-out. Kept on the session
121
- // because discovery is a network round trip and sign-out should not need
122
- // one — see signOut().
123
- revocationEndpoint: p.revocationEndpoint || null,
124
- refreshToken: tok.refresh_token || null, accessToken: tok.access_token,
125
- expiresAt: Date.now() + Math.max(30, (tok.expires_in || 300)) * 1000, webId };
126
- await idbPut('session', session);
127
- await idbDel('pending');
128
- // `returnTo` rides only on the handle this call returns: where the person
129
- // was when they left for the login page, for the caller to send them back.
130
- return Object.assign(sessionHandle(session), p.returnTo ? { returnTo: p.returnTo } : {});
131
- }
132
-
133
- /** Restore the session from IndexedDB — works in the page and the worker. */
134
- async function getSession() {
135
- const s = await idbGet('session');
136
- return s ? sessionHandle(s) : null;
137
- }
138
-
139
- /**
140
- * Sign out, and tell the issuer.
141
- *
142
- * Dropping the row locally leaves the REFRESH TOKEN alive at the pod for its
143
- * full life — signing out of a shared or borrowed browser would clear the
144
- * screen and nothing else, and anyone who had copied the token could go on
145
- * minting access tokens with it. RFC 7009: hand it back.
146
- *
147
- * Best effort, and the local teardown happens either way: an issuer that is
148
- * unreachable, or that publishes no revocation endpoint, must not be able to
149
- * keep somebody signed in.
150
- */
151
- async function signOut() {
152
- const s = await idbGet('session').catch(() => null);
153
- if (s?.revocationEndpoint && s.refreshToken) {
154
- try {
155
- await fetch(s.revocationEndpoint, {
156
- method: 'POST',
157
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
158
- body: new URLSearchParams({
159
- token: s.refreshToken, token_type_hint: 'refresh_token', client_id: s.client_id,
160
- }),
161
- });
162
- } catch { /* told if we could; leaving is not conditional on being heard */ }
163
- }
164
- await idbDel('session');
165
- await idbDel('pending');
166
- }
167
-
168
- function sessionHandle(s) {
169
- let { accessToken, expiresAt, refreshToken } = s;
170
- const jwkP = publicJwk(s.pair);
171
- const refresh = async () => {
172
- if (!refreshToken) throw new Error('session expired and there is no refresh token — sign in again');
173
- const jwk = await jwkP;
174
- let res;
175
- try {
176
- res = await fetch(s.tokenEndpoint, {
177
- method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded', dpop: await dpopProof(s.pair, jwk, 'POST', s.tokenEndpoint) },
178
- body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: s.client_id, scope: 'openid webid offline_access' }),
179
- });
180
- } catch (e) {
181
- // A renewal that never reached the server is not a refused one: the
182
- // session is still good, and saying "your pod refused the write" about
183
- // it sends whoever reads it looking in the wrong place. Under load a
184
- // server refuses the token endpoint first, and a refusal with no CORS
185
- // headers arrives here as the browser's bare "Failed to fetch".
186
- throw new Error(`${e.message} — renewing your sign-in at ${new URL(s.tokenEndpoint).host}`);
187
- }
188
- const tok = await res.json().catch(() => ({}));
189
- if (res.status === 429) {
190
- throw new Error(`${new URL(s.tokenEndpoint).host} is asking us to slow down — your sign-in could not be renewed just now`);
191
- }
192
- if (!res.ok || !tok.access_token) { await idbDel('session'); throw new Error('refresh failed — sign in again'); }
193
- accessToken = tok.access_token;
194
- expiresAt = Date.now() + Math.max(30, (tok.expires_in || 300)) * 1000;
195
- if (tok.refresh_token) refreshToken = tok.refresh_token;
196
- // Re-read before writing back. There is ONE 'session' row, and this handle
197
- // closed over the session it was made from — so a refresh that was already
198
- // in flight when the user signed in as somebody else would put the OLD
199
- // identity back, silently, over the new one. Only write if the row is still
200
- // the session this handle belongs to.
201
- const now = await idbGet('session');
202
- if (now && now.webId !== s.webId) {
203
- throw new Error('signed in as somebody else while this session was refreshing');
204
- }
205
- await idbPut('session', { ...s, accessToken, expiresAt, refreshToken });
206
- };
207
- const send = async (url, init) => {
208
- const jwk = await jwkP;
209
- const ath = b64u(await sha256(accessToken));
210
- const headers = { ...(init.headers || {}), authorization: `DPoP ${accessToken}`, dpop: await dpopProof(s.pair, jwk, (init.method || 'GET'), url, ath) };
211
- return fetch(url, { ...init, headers });
212
- };
213
- const authFetch = async (url, init = {}) => {
214
- if (Date.now() > expiresAt - 30_000) await refresh();
215
- const res = await send(url, init);
216
- // A token the pod REFUSES is not only ever an expired one: a pod that
217
- // restarted, a clock that drifted, a token the server stopped recognising
218
- // all answer 401 while the clock here still says the token is good — so the
219
- // renewal above never fires and every call fails from then on. Renew once
220
- // and ask again.
221
- if (res.status === 401 && refreshToken) {
222
- try { await refresh(); } catch { return res; }
223
- return send(url, init);
224
- }
225
- return res;
226
- };
227
- // `issuer` so a page that cannot renew can send the person back to the right
228
- // login without asking them for their address again.
229
- return { webId: s.webId, issuer: s.issuer, fetch: authFetch, refresh, signOut };
230
- }
231
-
232
- return { beginLogin, completeLogin, getSession, signOut };
233
- }
@@ -1,49 +0,0 @@
1
- {
2
- "name": "fediverse-account",
3
- "version": "0.2.1",
4
- "description": "Type a Fediverse handle or a WebID and get back an account you can act with: post, read its timeline, follow, reply, favourite, boost. Works out whether the account lives on a Mastodon-family server or on a Solid pod through FediPod, sends the person to sign in there, brings them back to where they were, and speaks to whichever it is.",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "Jeff Zucker",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/jeff-zucker/FediPod.git",
11
- "directory": "lib/session"
12
- },
13
- "homepage": "https://github.com/jeff-zucker/FediPod/tree/main/lib/session#readme",
14
- "keywords": [
15
- "fediverse",
16
- "activitypub",
17
- "mastodon",
18
- "solid",
19
- "solid-oidc",
20
- "webid",
21
- "login",
22
- "oauth",
23
- "fedipod"
24
- ],
25
- "files": [
26
- "fedi-account.mjs",
27
- "fedi-login.mjs",
28
- "oidc-session.mjs",
29
- "README.md"
30
- ],
31
- "exports": {
32
- ".": "./fedi-account.mjs",
33
- "./fedi-account.mjs": "./fedi-account.mjs",
34
- "./fedi-login.mjs": "./fedi-login.mjs",
35
- "./oidc-session.mjs": "./oidc-session.mjs"
36
- },
37
- "scripts": {
38
- "prepublishOnly": "node ../../scripts/check-pod-calls.mjs"
39
- },
40
- "dependencies": {},
41
- "peerDependencies": {
42
- "jsonld": "^9.0.0"
43
- },
44
- "peerDependenciesMeta": {
45
- "jsonld": {
46
- "optional": true
47
- }
48
- }
49
- }
@@ -1,213 +0,0 @@
1
- // guard.mjs — the browser-facing firewall. A loopback HTTP server is
2
- // reachable by any web page the user visits, and DNS rebinding lets such a
3
- // page keep its own origin while its requests land here — so binding to
4
- // loopback is NOT an access control. Two header checks close that door:
5
- //
6
- // Host must name a loopback authority we expect (or an operator-declared
7
- // host) — a rebound `evil.example` fails here.
8
- // Origin when present, must be one of those same authorities — a
9
- // cross-origin fetch/WebSocket fails here even without rebinding.
10
- //
11
- // AP_ALLOWED_HOSTS (comma-separated host[:port]) extends the set for
12
- // deliberate exposure (tailnet name, reverse-proxy domain).
13
- //
14
- // An agent also answers at <handle>.localhost, so each identity on a machine
15
- // gets an origin of its own — one browser storage jar per actor, instead of
16
- // every agent sharing localhost:<port> and asking which instance you meant.
17
- // `.localhost` is reserved by RFC 6761 and cannot be delegated in public DNS,
18
- // so it is not the rebinding hole this file exists to close; the label comes
19
- // from config, never from a request header, and matching stays exact-string.
20
-
21
- const LOOPBACK_NAMES = ['localhost', '127.0.0.1', '[::1]', '::1'];
22
-
23
- // A handle earns a named origin only when it is already a clean DNS label.
24
- // Nothing is rewritten: a mangled name would be a different origin from the
25
- // one we printed, and the user would land on a 403 with no way to see why.
26
- export function hostLabel(handle) {
27
- const s = String(handle || '').toLowerCase();
28
- return /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(s) ? s : null;
29
- }
30
-
31
- // The authorities that are this machine, and only this machine.
32
- //
33
- // The port-less form is a DIFFERENT origin: `http://localhost` means port 80
34
- // and `https://localhost` means 443. Emitting it whatever port we are on put
35
- // every other local server's pages inside our own origin set — a page served
36
- // from 127.0.0.1:80 sends `Origin: http://localhost`, which passed the Origin
37
- // check, and a form POST needs no preflight to reach a write route. So the
38
- // bare form is emitted only when it really is this server's own authority.
39
- export function loopbackAuthorities(port, labels = []) {
40
- const out = [];
41
- const bare = Number(port) === 80 || Number(port) === 443;
42
- for (const name of LOOPBACK_NAMES) {
43
- out.push(`${name}:${port}`);
44
- if (bare) out.push(name);
45
- }
46
- for (const l of labels) {
47
- const label = hostLabel(l);
48
- if (!label) continue;
49
- out.push(`${label}.localhost:${port}`);
50
- if (bare) out.push(`${label}.localhost`);
51
- }
52
- return out;
53
- }
54
-
55
- const LOOPBACK_ADDRESSES = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
56
-
57
- // Where the connection came FROM, as opposed to which authority it asked for.
58
- // Both listeners bind loopback, so today this is true of everything that
59
- // arrives — including a request relayed by a same-host reverse proxy, which is
60
- // exactly why it is not what keeps an exposed agent safe (see exposureProblem).
61
- // It is here so that binding to another address later cannot silently turn the
62
- // local-only routes into public ones.
63
- export function isLoopbackSocket(req) {
64
- const a = req?.socket?.remoteAddress;
65
- if (!a) return false;
66
- return LOOPBACK_ADDRESSES.has(a) || a.startsWith('127.');
67
- }
68
-
69
- // AP_ALLOWED_HOSTS is the documented way to put the agent on a tailnet name or
70
- // behind a reverse proxy. Doing so removes every transport signal that told the
71
- // operator apart from everyone else: the listeners bind loopback, so exposure
72
- // means something on this machine is relaying, and a stock
73
- // `proxy_pass http://127.0.0.1:<port>;` rewrites Host to a loopback authority
74
- // and connects from loopback. Every caller then looks local, which is what the
75
- // local-only routes and the password-less authorize path were trusting.
76
- //
77
- // A secret is all that is left, and AP_GATE_TOKEN is the one every route
78
- // checks — a uiPassword guards the OAuth login and nothing else. Returns a
79
- // reason string when the agent must not start, or null.
80
- export function exposureProblem({ allowedHosts, gateToken } = {}) {
81
- const exposed = String(allowedHosts || '').split(',').map(s => s.trim()).filter(Boolean);
82
- if (!exposed.length || gateToken) return null;
83
- return `AP_ALLOWED_HOSTS names ${exposed.join(', ')}, but AP_GATE_TOKEN is not set.\n`
84
- + 'An exposed agent cannot tell your requests from anyone else\'s — a reverse proxy\n'
85
- + 'rewrites Host to a loopback authority and connects from loopback, so every caller\n'
86
- + 'looks like this machine. Without a shared secret that means the admin API, the\n'
87
- + 'setup routes and the OAuth mint answer whoever reaches the address.\n\n'
88
- + 'Either set AP_GATE_TOKEN to a secret (send it as the x-dk-token header, or visit\n'
89
- + '?dk-token=<secret> once to be given the cookie), or unset AP_ALLOWED_HOSTS.';
90
- }
91
-
92
- export function allowedAuthorities(port, labels = []) {
93
- const extra = String(process.env.AP_ALLOWED_HOSTS || '')
94
- .split(',').map(s => s.trim().toLowerCase()).filter(Boolean);
95
- return new Set([...loopbackAuthorities(port, labels), ...extra]);
96
- }
97
-
98
- // The set has to stay live. startAdmin runs before connect, so an unconfigured
99
- // agent has no handle yet — and MastoApi consults this same object to decide
100
- // whether an OAuth redirect_uri is one of ours, so a snapshot taken at listen
101
- // time would reject the named origin for the life of the process.
102
- export class Authorities {
103
- constructor(port, handle = null, extraPorts = []) {
104
- this.port = port;
105
- // The https listener's port: same machine, same names, second authority.
106
- this.extraPorts = extraPorts.filter(Boolean);
107
- this.label = null;
108
- this.set = null;
109
- this.local = null;
110
- this.rebuild();
111
- this.setHandle(handle);
112
- }
113
-
114
- rebuild() {
115
- const labels = this.labels();
116
- this.set = allowedAuthorities(this.port, labels);
117
- this.local = new Set(loopbackAuthorities(this.port, labels));
118
- for (const p of this.extraPorts) {
119
- for (const a of loopbackAuthorities(p, labels)) { this.set.add(a); this.local.add(a); }
120
- }
121
- }
122
-
123
- // Returns true when the named origin actually changed.
124
- setHandle(handle) {
125
- const label = hostLabel(handle);
126
- if (label === this.label) return false;
127
- this.label = label;
128
- this.rebuild();
129
- return true;
130
- }
131
-
132
- labels() { return this.label ? [this.label] : []; }
133
-
134
- has(authority) { return this.set.has(authority); }
135
-
136
- // Machine-local, ignoring AP_ALLOWED_HOSTS: an agent deliberately exposed
137
- // under a tailnet name answers the fediverse there, but must not offer
138
- // account creation or config editing over it.
139
- isLocal(host) { return this.local.has(String(host || '').toLowerCase()); }
140
-
141
- // The Host header says which authority the client asked for; the socket says
142
- // where it came from. A local-only route wants both to agree. Neither
143
- // survives a same-host proxy on its own — exposureProblem is what covers
144
- // that case — but requiring both costs nothing and closes the plain forgery.
145
- isLocalRequest(req) {
146
- return isLoopbackSocket(req) && this.isLocal(req?.headers?.host);
147
- }
148
- }
149
-
150
- // The authority set for an identity served by somebody else's server: one
151
- // origin, the pod's own, and no notion of "this machine" at all. Nothing is
152
- // local here — a request from the next room and one from the far side of the
153
- // world arrive the same way — so the routes that were local-only are guarded
154
- // by the door's secret instead.
155
- export class FixedAuthorities {
156
- constructor(origin) {
157
- const u = new URL(origin);
158
- this.host = u.host.toLowerCase();
159
- this.port = Number(u.port) || (u.protocol === 'https:' ? 443 : 80);
160
- this.secure = u.protocol === 'https:';
161
- this.label = null;
162
- this.set = new Set([this.host]);
163
- this.extraPorts = [];
164
- }
165
-
166
- rebuild() { /* one origin, fixed at construction */ }
167
-
168
- setHandle() { return false; } // the origin does not follow the handle
169
-
170
- labels() { return []; }
171
-
172
- has(authority) { return this.set.has(String(authority || '').toLowerCase()); }
173
-
174
- isLocal() { return false; }
175
-
176
- isLocalRequest() { return false; }
177
-
178
- // What a page here may open a streaming socket to. Built from the pod's own
179
- // origin: the loopback list would leave the socket refused by the page's own
180
- // content-security-policy, with nothing in the console to say why.
181
- wsAuthorities() { return [`${this.secure ? 'wss' : 'ws'}://${this.host}`]; }
182
- }
183
-
184
- const authorityOf = (originHeader) => {
185
- try { return new URL(originHeader).host.toLowerCase(); } catch { return null; }
186
- };
187
-
188
- // Returns null when the request is acceptable, else a reason string.
189
- // `ignoreOrigin` keeps the Host check (what stops DNS rebinding) while letting
190
- // a foreign Origin through — the bearer-gated API answers any origin the way
191
- // a Mastodon server does, and a websocket with a live token likewise.
192
- export function checkRequest(req, allowed, { ignoreOrigin = false } = {}) {
193
- const host = String(req.headers.host || '').toLowerCase();
194
- if (!host || !allowed.has(host)) return `unexpected Host "${host || '(none)'}"`;
195
- if (ignoreOrigin) return null;
196
- const origin = req.headers.origin;
197
- // 'null' is what a sandboxed/file: page sends — never trusted here.
198
- if (origin && origin !== 'null') {
199
- const oh = authorityOf(origin);
200
- if (!oh || !allowed.has(oh)) return `cross-origin request from "${origin}"`;
201
- } else if (origin === 'null') {
202
- return 'opaque origin';
203
- }
204
- return null;
205
- }
206
-
207
- // Same-site navigations and typed URLs only — blocks a visited page from
208
- // driving a top-level navigation into an endpoint that mints credentials.
209
- // Absent header (older browsers, curl) is allowed: Host/Origin still apply.
210
- export function isCrossSiteNavigation(req) {
211
- const site = req.headers['sec-fetch-site'];
212
- return site === 'cross-site';
213
- }
@@ -1,9 +0,0 @@
1
- // links.mjs — reading RFC 8288 Link headers.
2
- //
3
- // The implementation moved into the pod library (lib/pod/links.mjs), which has
4
- // to carry it: that library runs unmodified in a service worker and may not
5
- // import anything above its own directory. This path stays so the dozen
6
- // importers around the project do not move, and so there is still one obvious
7
- // place to look for "how does this read a Link header".
8
-
9
- export { linkTargets, REL } from '../pod/links.mjs';