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