fedipod-server 0.29.0 → 0.30.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 (440) hide show
  1. package/README.md +11 -6
  2. package/dist/door.d.ts +4 -0
  3. package/dist/door.js +31 -0
  4. package/dist/fedipod.d.ts +16 -0
  5. package/dist/fedipod.js +13 -0
  6. package/dist/front.d.ts +3 -0
  7. package/dist/front.js +94 -0
  8. package/dist/handler.d.ts +36 -101
  9. package/dist/handler.js +85 -645
  10. package/dist/handler.jsonld +2 -30
  11. package/dist/identities.d.ts +62 -0
  12. package/dist/identities.js +236 -0
  13. package/dist/mounts.d.ts +43 -0
  14. package/dist/mounts.js +123 -0
  15. package/dist/opt-in.d.ts +46 -0
  16. package/dist/opt-in.js +176 -0
  17. package/package.json +10 -18
  18. package/lib/client/c2s.mjs +0 -636
  19. package/lib/client/localapi.mjs +0 -87
  20. package/lib/client/masto/accounts.mjs +0 -310
  21. package/lib/client/masto/body.mjs +0 -156
  22. package/lib/client/masto/index.mjs +0 -206
  23. package/lib/client/masto/instance.mjs +0 -112
  24. package/lib/client/masto/media.mjs +0 -79
  25. package/lib/client/masto/oauth.mjs +0 -656
  26. package/lib/client/masto/render.mjs +0 -518
  27. package/lib/client/masto/statuses.mjs +0 -372
  28. package/lib/client/masto/timelines.mjs +0 -438
  29. package/lib/client/oidc-auth.mjs +0 -62
  30. package/lib/client/streaming.mjs +0 -154
  31. package/lib/client/webpush.mjs +0 -98
  32. package/lib/connections/acctfeed.mjs +0 -257
  33. package/lib/connections/atproto.mjs +0 -309
  34. package/lib/connections/bskyfeed.mjs +0 -243
  35. package/lib/connections/bskygroup.mjs +0 -162
  36. package/lib/connections/fediacct.mjs +0 -253
  37. package/lib/connections/import.mjs +0 -357
  38. package/lib/connections/tagfeed.mjs +0 -212
  39. package/lib/connections/vault.mjs +0 -114
  40. package/lib/core/as2.mjs +0 -206
  41. package/lib/core/contexts/activitystreams.json +0 -379
  42. package/lib/core/contexts/anno.json +0 -126
  43. package/lib/core/contexts/did-v1.json +0 -57
  44. package/lib/core/contexts/fep-5711.json +0 -36
  45. package/lib/core/contexts/gotosocial.json +0 -86
  46. package/lib/core/contexts/identity-v1.json +0 -152
  47. package/lib/core/contexts/index.mjs +0 -63
  48. package/lib/core/contexts/join-lemmy.json +0 -33
  49. package/lib/core/contexts/joinmastodon.json +0 -28
  50. package/lib/core/contexts/litepub-0.1.json +0 -60
  51. package/lib/core/contexts/map.json +0 -19
  52. package/lib/core/contexts/miscellany.json +0 -19
  53. package/lib/core/contexts/quotes.json +0 -18
  54. package/lib/core/contexts/schemaorg.json +0 -8845
  55. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  56. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  57. package/lib/core/contexts/security-multikey-v1.json +0 -35
  58. package/lib/core/contexts/security-v1.json +0 -74
  59. package/lib/core/contexts/webfinger.json +0 -10
  60. package/lib/core/deliver.mjs +0 -387
  61. package/lib/core/graphview.mjs +0 -327
  62. package/lib/core/intake/activities.mjs +0 -649
  63. package/lib/core/intake/activity.mjs +0 -318
  64. package/lib/core/intake/channel.mjs +0 -144
  65. package/lib/core/intake/group.mjs +0 -258
  66. package/lib/core/intake/index.mjs +0 -732
  67. package/lib/core/intake/notes.mjs +0 -367
  68. package/lib/core/intake/verify.mjs +0 -142
  69. package/lib/core/keys.mjs +0 -153
  70. package/lib/core/lease.mjs +0 -206
  71. package/lib/core/place.mjs +0 -82
  72. package/lib/core/polls.mjs +0 -105
  73. package/lib/core/profile-page.mjs +0 -104
  74. package/lib/core/proof.mjs +0 -79
  75. package/lib/core/publisher/collections.mjs +0 -272
  76. package/lib/core/publisher/index.mjs +0 -522
  77. package/lib/core/publisher/notes.mjs +0 -519
  78. package/lib/core/publisher/own.mjs +0 -143
  79. package/lib/core/publisher/questions.mjs +0 -241
  80. package/lib/core/publisher/restore.mjs +0 -224
  81. package/lib/core/shapes/activitystreams.ttl +0 -157
  82. package/lib/core/shapes/index.mjs +0 -132
  83. package/lib/core/shapes/shapes-text.mjs +0 -13
  84. package/lib/core/social.mjs +0 -561
  85. package/lib/core/storage.mjs +0 -176
  86. package/lib/core/store.mjs +0 -578
  87. package/lib/core/wire-quotes.mjs +0 -71
  88. package/lib/core/wire.mjs +0 -925
  89. package/lib/device/account.mjs +0 -87
  90. package/lib/device/admin/index.mjs +0 -13
  91. package/lib/device/admin/origins.mjs +0 -35
  92. package/lib/device/admin/routes/connections.mjs +0 -144
  93. package/lib/device/admin/routes/gateway.mjs +0 -199
  94. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  95. package/lib/device/admin/routes/owner.mjs +0 -322
  96. package/lib/device/admin/routes/setup.mjs +0 -428
  97. package/lib/device/admin/routes/social.mjs +0 -188
  98. package/lib/device/admin/server.mjs +0 -95
  99. package/lib/device/admin/static.mjs +0 -247
  100. package/lib/device/admin/surface.mjs +0 -315
  101. package/lib/device/certs.mjs +0 -281
  102. package/lib/device/cli/commands/account.mjs +0 -586
  103. package/lib/device/cli/commands/run.mjs +0 -278
  104. package/lib/device/cli/commands/service.mjs +0 -221
  105. package/lib/device/cli/commands/setup.mjs +0 -489
  106. package/lib/device/cli/commands/state.mjs +0 -561
  107. package/lib/device/cli/context.mjs +0 -288
  108. package/lib/device/export-collections.mjs +0 -161
  109. package/lib/device/gateway-move.mjs +0 -58
  110. package/lib/device/home.mjs +0 -161
  111. package/lib/device/migrate.mjs +0 -115
  112. package/lib/device/ports.mjs +0 -27
  113. package/lib/device/remote.mjs +0 -54
  114. package/lib/device/setup.mjs +0 -496
  115. package/lib/device/update.mjs +0 -96
  116. package/lib/gateway/caches.mjs +0 -36
  117. package/lib/gateway/directory.mjs +0 -90
  118. package/lib/gateway/front-core.mjs +0 -966
  119. package/lib/gateway/gateway-core.mjs +0 -244
  120. package/lib/gateway/headers.mjs +0 -49
  121. package/lib/gateway/httpsig.mjs +0 -159
  122. package/lib/gateway/notices.mjs +0 -84
  123. package/lib/gateway/quiet.mjs +0 -189
  124. package/lib/gateway/relay-extras.mjs +0 -89
  125. package/lib/gateway/token-claims.mjs +0 -16
  126. package/lib/pod/README.md +0 -61
  127. package/lib/pod/actor.mjs +0 -96
  128. package/lib/pod/collection.mjs +0 -77
  129. package/lib/pod/containers.mjs +0 -140
  130. package/lib/pod/discovery.mjs +0 -46
  131. package/lib/pod/featured.mjs +0 -18
  132. package/lib/pod/followers.mjs +0 -24
  133. package/lib/pod/following.mjs +0 -10
  134. package/lib/pod/http.mjs +0 -74
  135. package/lib/pod/inbox.mjs +0 -166
  136. package/lib/pod/links.mjs +0 -35
  137. package/lib/pod/location.mjs +0 -52
  138. package/lib/pod/media.mjs +0 -27
  139. package/lib/pod/notes.mjs +0 -78
  140. package/lib/pod/notifications.mjs +0 -89
  141. package/lib/pod/outbox.mjs +0 -18
  142. package/lib/pod/package.json +0 -19
  143. package/lib/pod/policy.mjs +0 -58
  144. package/lib/pod/private.mjs +0 -19
  145. package/lib/pod/root.mjs +0 -104
  146. package/lib/pod/state.mjs +0 -40
  147. package/lib/pod/transport.mjs +0 -674
  148. package/lib/pod/type-index.mjs +0 -101
  149. package/lib/pod/urls.mjs +0 -91
  150. package/lib/server/embed.mjs +0 -424
  151. package/lib/session/README.md +0 -137
  152. package/lib/session/demo.html +0 -94
  153. package/lib/session/fedi-account.mjs +0 -417
  154. package/lib/session/fedi-login.mjs +0 -123
  155. package/lib/session/oidc-session.mjs +0 -233
  156. package/lib/session/package.json +0 -49
  157. package/lib/shared/guard.mjs +0 -213
  158. package/lib/shared/links.mjs +0 -9
  159. package/lib/shared/safefetch.mjs +0 -235
  160. package/lib/shared/ua.mjs +0 -29
  161. package/phanpy/dist/404.html +0 -40
  162. package/phanpy/dist/_headers +0 -2
  163. package/phanpy/dist/apple-touch-icon.png +0 -0
  164. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  165. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  166. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  167. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  168. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  169. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  170. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  171. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  172. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  173. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  174. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  175. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  176. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  177. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  178. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  179. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  180. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  181. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  182. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  183. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  184. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  185. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  186. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  187. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  188. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  189. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  190. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  191. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  192. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  193. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  197. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  198. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  199. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  200. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  201. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  202. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  203. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  204. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  205. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  206. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  207. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  208. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  209. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  210. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  211. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  212. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  213. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  214. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  215. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  216. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  217. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  218. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  219. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  220. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  221. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  222. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  223. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  224. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  225. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  226. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  227. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  228. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  229. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  230. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  231. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  232. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  233. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  234. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  235. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  236. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  237. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  238. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  239. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  240. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  241. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  242. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  243. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  244. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  245. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  246. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  247. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  248. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  249. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  250. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  251. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  252. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  253. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  254. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  255. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  256. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  257. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  258. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  259. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  260. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  261. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  262. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  263. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  264. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  265. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  266. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  267. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  268. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  269. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  270. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  271. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  272. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  273. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  274. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  275. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  276. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  277. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  278. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  279. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  280. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  281. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  282. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  283. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  284. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  285. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  286. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  287. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  288. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  289. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  290. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  291. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  292. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  293. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  294. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  295. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  296. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  297. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  298. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  299. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  300. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  301. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  302. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  303. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  304. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  305. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  306. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  307. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  308. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  309. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  310. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  311. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  312. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  313. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  314. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  315. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  316. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  317. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  318. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  319. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  320. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  321. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  322. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  323. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  324. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  325. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  326. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  327. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  328. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  329. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  330. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  331. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  332. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  333. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  334. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  336. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  337. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  338. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  339. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  340. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  341. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  342. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  343. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  344. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  345. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  346. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  347. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  348. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  349. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  350. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  351. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  352. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  353. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  354. package/phanpy/dist/compose/index.html +0 -20
  355. package/phanpy/dist/favicon.ico +0 -0
  356. package/phanpy/dist/index.html +0 -73
  357. package/phanpy/dist/logo-192.png +0 -0
  358. package/phanpy/dist/logo-512.png +0 -0
  359. package/phanpy/dist/logo-badge-72.png +0 -0
  360. package/phanpy/dist/logo-maskable-512.png +0 -0
  361. package/phanpy/dist/logo-monochrome-512.png +0 -0
  362. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  363. package/phanpy/dist/manifest.webmanifest +0 -1
  364. package/phanpy/dist/og-image-2.jpg +0 -0
  365. package/phanpy/dist/og-image.png +0 -0
  366. package/phanpy/dist/robots.txt +0 -2
  367. package/phanpy/dist/sw.js +0 -2
  368. package/phanpy/dist/sw.js.map +0 -1
  369. package/phanpy/dist/version.json +0 -1
  370. package/run-agent.mjs +0 -899
  371. package/vendor/gate.cjs +0 -138
  372. package/vendor/idp-grant.cjs +0 -394
  373. package/vendor/jfetch.cjs +0 -16
  374. package/web/admin/actors.js +0 -147
  375. package/web/admin/bar.css +0 -126
  376. package/web/admin/bar.js +0 -99
  377. package/web/admin/client/client.js +0 -66
  378. package/web/admin/client/index.html +0 -69
  379. package/web/admin/client-pick.js +0 -79
  380. package/web/admin/common.js +0 -23
  381. package/web/admin/connections.js +0 -112
  382. package/web/admin/gateway.js +0 -133
  383. package/web/admin/group.js +0 -258
  384. package/web/admin/index.html +0 -612
  385. package/web/admin/notices-bar.js +0 -90
  386. package/web/admin/oauth-signin.mjs +0 -37
  387. package/web/admin/record.js +0 -393
  388. package/web/admin/setup/index.html +0 -257
  389. package/web/admin/setup/setup.js +0 -339
  390. package/web/admin/tokens.css +0 -68
  391. package/web/admin/upkeep.js +0 -177
  392. package/web/admin/window.css +0 -55
  393. package/web/admin/window.js +0 -136
  394. package/web/app/README.md +0 -81
  395. package/web/app/admin-facade.mjs +0 -588
  396. package/web/app/agent.mjs +0 -672
  397. package/web/app/atproto-browser.mjs +0 -86
  398. package/web/app/authorize_interaction.html +0 -56
  399. package/web/app/authorize_interaction.js +0 -69
  400. package/web/app/boot.mjs +0 -616
  401. package/web/app/deliver-relay.mjs +0 -129
  402. package/web/app/dist/boot.js +0 -34562
  403. package/web/app/dist/boot.js.map +0 -7
  404. package/web/app/dist/sw.js +0 -73456
  405. package/web/app/dist/sw.js.map +0 -7
  406. package/web/app/fediacct-browser.mjs +0 -146
  407. package/web/app/gateway-move.mjs +0 -60
  408. package/web/app/idb-kv.mjs +0 -35
  409. package/web/app/index.html +0 -258
  410. package/web/app/keys-browser.mjs +0 -71
  411. package/web/app/keystore.mjs +0 -82
  412. package/web/app/oidc-session.mjs +0 -6
  413. package/web/app/pod-auth.mjs +0 -248
  414. package/web/app/pod-remote.mjs +0 -84
  415. package/web/app/shims/_globals.mjs +0 -2
  416. package/web/app/shims/fedify-sig.mjs +0 -77
  417. package/web/app/shims/node-crypto.mjs +0 -113
  418. package/web/app/shims/node-fs.mjs +0 -17
  419. package/web/app/shims/node-path.mjs +0 -19
  420. package/web/app/shims/node-url.mjs +0 -7
  421. package/web/app/shims/prelude.js +0 -58
  422. package/web/app/shims/safefetch.mjs +0 -58
  423. package/web/app/shims/shapes-text.mjs +0 -8
  424. package/web/app/shims/web-push.mjs +0 -14
  425. package/web/app/signup.mjs +0 -357
  426. package/web/app/sw-src.mjs +0 -220
  427. package/web/app/update.js +0 -70
  428. package/web/front/admin.html +0 -55
  429. package/web/front/admin.js +0 -89
  430. package/web/front/install.sh +0 -69
  431. package/web/front/new-account.html +0 -35
  432. package/web/front/new-account.js +0 -13
  433. package/web/front/notices.html +0 -69
  434. package/web/front/notices.js +0 -107
  435. package/web/front/run.html +0 -64
  436. package/web/front/run.js +0 -221
  437. package/web/front/solid-oidc-client.js +0 -6
  438. package/web/icons/forum.svg +0 -11
  439. package/web/icons/gateway.svg +0 -14
  440. 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';