fedipod-server 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +16 -6
  2. package/dist/claims.d.ts +2 -0
  3. package/dist/claims.js +13 -4
  4. package/dist/door.d.ts +4 -0
  5. package/dist/door.js +31 -0
  6. package/dist/fedipod.d.ts +15 -0
  7. package/dist/fedipod.js +13 -0
  8. package/dist/front.d.ts +3 -0
  9. package/dist/front.js +92 -0
  10. package/dist/handler.d.ts +36 -101
  11. package/dist/handler.js +93 -644
  12. package/dist/handler.jsonld +2 -30
  13. package/dist/identities.d.ts +71 -0
  14. package/dist/identities.js +236 -0
  15. package/dist/mounts.d.ts +43 -0
  16. package/dist/mounts.js +123 -0
  17. package/dist/opt-in.d.ts +46 -0
  18. package/dist/opt-in.js +176 -0
  19. package/dist/private-read.d.ts +4 -0
  20. package/dist/private-read.js +34 -0
  21. package/package.json +12 -20
  22. package/lib/client/c2s.mjs +0 -636
  23. package/lib/client/localapi.mjs +0 -87
  24. package/lib/client/masto/accounts.mjs +0 -310
  25. package/lib/client/masto/body.mjs +0 -156
  26. package/lib/client/masto/index.mjs +0 -206
  27. package/lib/client/masto/instance.mjs +0 -112
  28. package/lib/client/masto/media.mjs +0 -79
  29. package/lib/client/masto/oauth.mjs +0 -656
  30. package/lib/client/masto/render.mjs +0 -518
  31. package/lib/client/masto/statuses.mjs +0 -372
  32. package/lib/client/masto/timelines.mjs +0 -438
  33. package/lib/client/oidc-auth.mjs +0 -62
  34. package/lib/client/streaming.mjs +0 -154
  35. package/lib/client/webpush.mjs +0 -98
  36. package/lib/connections/acctfeed.mjs +0 -257
  37. package/lib/connections/atproto.mjs +0 -309
  38. package/lib/connections/bskyfeed.mjs +0 -243
  39. package/lib/connections/bskygroup.mjs +0 -162
  40. package/lib/connections/fediacct.mjs +0 -253
  41. package/lib/connections/import.mjs +0 -357
  42. package/lib/connections/tagfeed.mjs +0 -212
  43. package/lib/connections/vault.mjs +0 -114
  44. package/lib/core/as2.mjs +0 -206
  45. package/lib/core/contexts/activitystreams.json +0 -379
  46. package/lib/core/contexts/anno.json +0 -126
  47. package/lib/core/contexts/did-v1.json +0 -57
  48. package/lib/core/contexts/fep-5711.json +0 -36
  49. package/lib/core/contexts/gotosocial.json +0 -86
  50. package/lib/core/contexts/identity-v1.json +0 -152
  51. package/lib/core/contexts/index.mjs +0 -63
  52. package/lib/core/contexts/join-lemmy.json +0 -33
  53. package/lib/core/contexts/joinmastodon.json +0 -28
  54. package/lib/core/contexts/litepub-0.1.json +0 -60
  55. package/lib/core/contexts/map.json +0 -19
  56. package/lib/core/contexts/miscellany.json +0 -19
  57. package/lib/core/contexts/quotes.json +0 -18
  58. package/lib/core/contexts/schemaorg.json +0 -8845
  59. package/lib/core/contexts/security-data-integrity-v1.json +0 -78
  60. package/lib/core/contexts/security-data-integrity-v2.json +0 -81
  61. package/lib/core/contexts/security-multikey-v1.json +0 -35
  62. package/lib/core/contexts/security-v1.json +0 -74
  63. package/lib/core/contexts/webfinger.json +0 -10
  64. package/lib/core/deliver.mjs +0 -387
  65. package/lib/core/graphview.mjs +0 -327
  66. package/lib/core/intake/activities.mjs +0 -649
  67. package/lib/core/intake/activity.mjs +0 -318
  68. package/lib/core/intake/channel.mjs +0 -144
  69. package/lib/core/intake/group.mjs +0 -258
  70. package/lib/core/intake/index.mjs +0 -732
  71. package/lib/core/intake/notes.mjs +0 -367
  72. package/lib/core/intake/verify.mjs +0 -142
  73. package/lib/core/keys.mjs +0 -153
  74. package/lib/core/lease.mjs +0 -206
  75. package/lib/core/place.mjs +0 -82
  76. package/lib/core/polls.mjs +0 -105
  77. package/lib/core/profile-page.mjs +0 -104
  78. package/lib/core/proof.mjs +0 -79
  79. package/lib/core/publisher/collections.mjs +0 -272
  80. package/lib/core/publisher/index.mjs +0 -522
  81. package/lib/core/publisher/notes.mjs +0 -519
  82. package/lib/core/publisher/own.mjs +0 -143
  83. package/lib/core/publisher/questions.mjs +0 -241
  84. package/lib/core/publisher/restore.mjs +0 -224
  85. package/lib/core/shapes/activitystreams.ttl +0 -157
  86. package/lib/core/shapes/index.mjs +0 -132
  87. package/lib/core/shapes/shapes-text.mjs +0 -13
  88. package/lib/core/social.mjs +0 -561
  89. package/lib/core/storage.mjs +0 -176
  90. package/lib/core/store.mjs +0 -578
  91. package/lib/core/wire-quotes.mjs +0 -71
  92. package/lib/core/wire.mjs +0 -925
  93. package/lib/device/account.mjs +0 -87
  94. package/lib/device/admin/index.mjs +0 -13
  95. package/lib/device/admin/origins.mjs +0 -35
  96. package/lib/device/admin/routes/connections.mjs +0 -144
  97. package/lib/device/admin/routes/gateway.mjs +0 -199
  98. package/lib/device/admin/routes/lifecycle.mjs +0 -200
  99. package/lib/device/admin/routes/owner.mjs +0 -322
  100. package/lib/device/admin/routes/setup.mjs +0 -428
  101. package/lib/device/admin/routes/social.mjs +0 -188
  102. package/lib/device/admin/server.mjs +0 -95
  103. package/lib/device/admin/static.mjs +0 -247
  104. package/lib/device/admin/surface.mjs +0 -315
  105. package/lib/device/certs.mjs +0 -281
  106. package/lib/device/cli/commands/account.mjs +0 -586
  107. package/lib/device/cli/commands/run.mjs +0 -278
  108. package/lib/device/cli/commands/service.mjs +0 -221
  109. package/lib/device/cli/commands/setup.mjs +0 -489
  110. package/lib/device/cli/commands/state.mjs +0 -561
  111. package/lib/device/cli/context.mjs +0 -288
  112. package/lib/device/export-collections.mjs +0 -161
  113. package/lib/device/gateway-move.mjs +0 -58
  114. package/lib/device/home.mjs +0 -161
  115. package/lib/device/migrate.mjs +0 -115
  116. package/lib/device/ports.mjs +0 -27
  117. package/lib/device/remote.mjs +0 -54
  118. package/lib/device/setup.mjs +0 -496
  119. package/lib/device/update.mjs +0 -96
  120. package/lib/gateway/caches.mjs +0 -36
  121. package/lib/gateway/directory.mjs +0 -90
  122. package/lib/gateway/front-core.mjs +0 -966
  123. package/lib/gateway/gateway-core.mjs +0 -244
  124. package/lib/gateway/headers.mjs +0 -49
  125. package/lib/gateway/httpsig.mjs +0 -159
  126. package/lib/gateway/notices.mjs +0 -84
  127. package/lib/gateway/quiet.mjs +0 -189
  128. package/lib/gateway/relay-extras.mjs +0 -89
  129. package/lib/gateway/token-claims.mjs +0 -16
  130. package/lib/pod/README.md +0 -61
  131. package/lib/pod/actor.mjs +0 -96
  132. package/lib/pod/collection.mjs +0 -77
  133. package/lib/pod/containers.mjs +0 -140
  134. package/lib/pod/discovery.mjs +0 -46
  135. package/lib/pod/featured.mjs +0 -18
  136. package/lib/pod/followers.mjs +0 -24
  137. package/lib/pod/following.mjs +0 -10
  138. package/lib/pod/http.mjs +0 -74
  139. package/lib/pod/inbox.mjs +0 -166
  140. package/lib/pod/links.mjs +0 -35
  141. package/lib/pod/location.mjs +0 -52
  142. package/lib/pod/media.mjs +0 -27
  143. package/lib/pod/notes.mjs +0 -78
  144. package/lib/pod/notifications.mjs +0 -89
  145. package/lib/pod/outbox.mjs +0 -18
  146. package/lib/pod/package.json +0 -19
  147. package/lib/pod/policy.mjs +0 -58
  148. package/lib/pod/private.mjs +0 -19
  149. package/lib/pod/root.mjs +0 -104
  150. package/lib/pod/state.mjs +0 -40
  151. package/lib/pod/transport.mjs +0 -674
  152. package/lib/pod/type-index.mjs +0 -101
  153. package/lib/pod/urls.mjs +0 -91
  154. package/lib/server/embed.mjs +0 -424
  155. package/lib/session/README.md +0 -137
  156. package/lib/session/demo.html +0 -94
  157. package/lib/session/fedi-account.mjs +0 -417
  158. package/lib/session/fedi-login.mjs +0 -123
  159. package/lib/session/oidc-session.mjs +0 -233
  160. package/lib/session/package.json +0 -49
  161. package/lib/shared/guard.mjs +0 -213
  162. package/lib/shared/links.mjs +0 -9
  163. package/lib/shared/safefetch.mjs +0 -235
  164. package/lib/shared/ua.mjs +0 -29
  165. package/phanpy/dist/404.html +0 -40
  166. package/phanpy/dist/_headers +0 -2
  167. package/phanpy/dist/apple-touch-icon.png +0 -0
  168. package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
  169. package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
  170. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
  171. package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
  172. package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
  173. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
  174. package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
  175. package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
  176. package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
  177. package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
  178. package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
  179. package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
  180. package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
  181. package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
  182. package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
  183. package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
  184. package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
  185. package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
  186. package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
  187. package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
  188. package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
  189. package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
  190. package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
  191. package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
  192. package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
  193. package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
  194. package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
  195. package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
  196. package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
  197. package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
  198. package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
  199. package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
  200. package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
  201. package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
  202. package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
  203. package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
  204. package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
  205. package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
  206. package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
  207. package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
  208. package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
  209. package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
  210. package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
  211. package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
  212. package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
  213. package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
  214. package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
  215. package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
  216. package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
  217. package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
  218. package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
  219. package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
  220. package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
  221. package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
  222. package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
  223. package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
  224. package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
  225. package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
  226. package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
  227. package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
  228. package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
  229. package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
  230. package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
  231. package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
  232. package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
  233. package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
  234. package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
  235. package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
  236. package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
  237. package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
  238. package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
  239. package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
  240. package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
  241. package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
  242. package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
  243. package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
  244. package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
  245. package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
  246. package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
  247. package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
  248. package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
  249. package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
  250. package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
  251. package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
  252. package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
  253. package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
  254. package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
  255. package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
  256. package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
  257. package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
  258. package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
  259. package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
  260. package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
  261. package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
  262. package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
  263. package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
  264. package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
  265. package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
  266. package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
  267. package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
  268. package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
  269. package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
  270. package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
  271. package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
  272. package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
  273. package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
  274. package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
  275. package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
  276. package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
  277. package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
  278. package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
  279. package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
  280. package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
  281. package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
  282. package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
  283. package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
  284. package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
  285. package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
  286. package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
  287. package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
  288. package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
  289. package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
  290. package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
  291. package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
  292. package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
  293. package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
  294. package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
  295. package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
  296. package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
  297. package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
  298. package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
  299. package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
  300. package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
  301. package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
  302. package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
  303. package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
  304. package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
  305. package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
  306. package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
  307. package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
  308. package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
  309. package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
  310. package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
  311. package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
  312. package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
  313. package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
  314. package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
  315. package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
  316. package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
  317. package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
  318. package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
  319. package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
  320. package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
  321. package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
  322. package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
  323. package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
  324. package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
  325. package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
  326. package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
  327. package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
  328. package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
  329. package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
  330. package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
  331. package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
  332. package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
  333. package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
  334. package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
  335. package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
  336. package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
  337. package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
  338. package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
  339. package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
  340. package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
  341. package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
  342. package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
  343. package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
  344. package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
  345. package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
  346. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
  347. package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
  348. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
  349. package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
  350. package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
  351. package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
  352. package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
  353. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
  354. package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
  355. package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
  356. package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
  357. package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
  358. package/phanpy/dist/compose/index.html +0 -20
  359. package/phanpy/dist/favicon.ico +0 -0
  360. package/phanpy/dist/index.html +0 -73
  361. package/phanpy/dist/logo-192.png +0 -0
  362. package/phanpy/dist/logo-512.png +0 -0
  363. package/phanpy/dist/logo-badge-72.png +0 -0
  364. package/phanpy/dist/logo-maskable-512.png +0 -0
  365. package/phanpy/dist/logo-monochrome-512.png +0 -0
  366. package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
  367. package/phanpy/dist/manifest.webmanifest +0 -1
  368. package/phanpy/dist/og-image-2.jpg +0 -0
  369. package/phanpy/dist/og-image.png +0 -0
  370. package/phanpy/dist/robots.txt +0 -2
  371. package/phanpy/dist/sw.js +0 -2
  372. package/phanpy/dist/sw.js.map +0 -1
  373. package/phanpy/dist/version.json +0 -1
  374. package/run-agent.mjs +0 -899
  375. package/vendor/gate.cjs +0 -138
  376. package/vendor/idp-grant.cjs +0 -394
  377. package/vendor/jfetch.cjs +0 -16
  378. package/web/admin/actors.js +0 -147
  379. package/web/admin/bar.css +0 -126
  380. package/web/admin/bar.js +0 -99
  381. package/web/admin/client/client.js +0 -66
  382. package/web/admin/client/index.html +0 -69
  383. package/web/admin/client-pick.js +0 -79
  384. package/web/admin/common.js +0 -23
  385. package/web/admin/connections.js +0 -112
  386. package/web/admin/gateway.js +0 -133
  387. package/web/admin/group.js +0 -258
  388. package/web/admin/index.html +0 -612
  389. package/web/admin/notices-bar.js +0 -90
  390. package/web/admin/oauth-signin.mjs +0 -37
  391. package/web/admin/record.js +0 -393
  392. package/web/admin/setup/index.html +0 -257
  393. package/web/admin/setup/setup.js +0 -339
  394. package/web/admin/tokens.css +0 -68
  395. package/web/admin/upkeep.js +0 -177
  396. package/web/admin/window.css +0 -55
  397. package/web/admin/window.js +0 -136
  398. package/web/app/README.md +0 -81
  399. package/web/app/admin-facade.mjs +0 -588
  400. package/web/app/agent.mjs +0 -672
  401. package/web/app/atproto-browser.mjs +0 -86
  402. package/web/app/authorize_interaction.html +0 -56
  403. package/web/app/authorize_interaction.js +0 -69
  404. package/web/app/boot.mjs +0 -616
  405. package/web/app/deliver-relay.mjs +0 -129
  406. package/web/app/dist/boot.js +0 -34562
  407. package/web/app/dist/boot.js.map +0 -7
  408. package/web/app/dist/sw.js +0 -73456
  409. package/web/app/dist/sw.js.map +0 -7
  410. package/web/app/fediacct-browser.mjs +0 -146
  411. package/web/app/gateway-move.mjs +0 -60
  412. package/web/app/idb-kv.mjs +0 -35
  413. package/web/app/index.html +0 -258
  414. package/web/app/keys-browser.mjs +0 -71
  415. package/web/app/keystore.mjs +0 -82
  416. package/web/app/oidc-session.mjs +0 -6
  417. package/web/app/pod-auth.mjs +0 -248
  418. package/web/app/pod-remote.mjs +0 -84
  419. package/web/app/shims/_globals.mjs +0 -2
  420. package/web/app/shims/fedify-sig.mjs +0 -77
  421. package/web/app/shims/node-crypto.mjs +0 -113
  422. package/web/app/shims/node-fs.mjs +0 -17
  423. package/web/app/shims/node-path.mjs +0 -19
  424. package/web/app/shims/node-url.mjs +0 -7
  425. package/web/app/shims/prelude.js +0 -58
  426. package/web/app/shims/safefetch.mjs +0 -58
  427. package/web/app/shims/shapes-text.mjs +0 -8
  428. package/web/app/shims/web-push.mjs +0 -14
  429. package/web/app/signup.mjs +0 -357
  430. package/web/app/sw-src.mjs +0 -220
  431. package/web/app/update.js +0 -70
  432. package/web/front/admin.html +0 -55
  433. package/web/front/admin.js +0 -89
  434. package/web/front/install.sh +0 -69
  435. package/web/front/new-account.html +0 -35
  436. package/web/front/new-account.js +0 -13
  437. package/web/front/notices.html +0 -69
  438. package/web/front/notices.js +0 -107
  439. package/web/front/run.html +0 -64
  440. package/web/front/run.js +0 -221
  441. package/web/front/solid-oidc-client.js +0 -6
  442. package/web/icons/forum.svg +0 -11
  443. package/web/icons/gateway.svg +0 -14
  444. package/web/icons/local.svg +0 -11
@@ -1,244 +0,0 @@
1
- // gateway-core.mjs — the runtime-agnostic logic of an inbox gateway: verify a
2
- // delivery at the door (where the HTTP-Signature headers still exist), drop
3
- // spam and forgeries at the edge, and forward what remains — with a signed
4
- // verification receipt — into the pod inbox for the ordinary drain.
5
- //
6
- // This holds NO signing key. It verifies with remotes' PUBLIC keys, writes to
7
- // the pod inbox with an Append-only credential the adapter supplies, and stamps
8
- // the receipt with a shared HMAC secret. A compromise here can inject inbox
9
- // items (which still meet verify-by-dereference in the drain) but cannot
10
- // impersonate the actor, read the private tree, or post as it.
11
- //
12
- // `netlify/functions/inbox.mjs` is a thin adapter over this; an agent-side
13
- // endpoint could be another. The core is what the smoke suite exercises.
14
-
15
- import crypto from 'node:crypto';
16
- import { verifyHttpSignature, makeSafeLoader, makeReceipt, signReceipt } from './httpsig.mjs';
17
- import * as inbox from '../pod/inbox.mjs';
18
- import { senderKeys } from './caches.mjs';
19
-
20
- const DEFAULT_MAX_BYTES = 512 * 1024; // mirror intake.mjs MAX_ITEM_BYTES
21
-
22
- // Control activities are the message itself and must always pass — you want
23
- // Follows from strangers. Only CONTENT is subject to the concerns-us drop,
24
- // and to a pause: an account nobody is reading still takes its follows.
25
- export const CONTROL = new Set(['Follow', 'Undo', 'Accept', 'Reject', 'Delete', 'Move',
26
- 'Add', 'Remove', 'Block']);
27
- export const isControl = (type) => CONTROL.has(type);
28
-
29
- const idOf = (v) => (typeof v === 'string' ? v : v?.id) || null;
30
- const sha256hex = (s) => crypto.createHash('sha256').update(s).digest('hex');
31
-
32
- function httpUrl(u) {
33
- try { const p = new URL(u).protocol; return p === 'http:' || p === 'https:'; } catch { return false; }
34
- }
35
-
36
- // Mirror of intake.isBlocked over the policy doc's public blocklist mirror.
37
- function isBlocked(actor, blocklist = {}) {
38
- if (!actor) return true;
39
- if ((blocklist.actors || []).includes(actor)) return true;
40
- // hostname, not host: the agent's own isBlocked strips the port, and a door
41
- // that keeps it lets a blocked domain back in on a non-default port.
42
- let host; try { host = new URL(actor).hostname; } catch { return false; }
43
- return (blocklist.domains || []).some(d => host === d || host.endsWith('.' + d));
44
- }
45
-
46
- // The edge form of intake.concernsUs — pure addressing, no I/O. Conservative:
47
- // only DROPS content it is confident does not concern the identity. A GROUP
48
- // owns conversations under anything it has carried, which the keyless edge
49
- // cannot see, so a group defers the whole check to the drain (returns true).
50
- function concernsUsAtEdge(activity, ident) {
51
- if (ident.kind === 'group') return true;
52
- const actor = idOf(activity.actor);
53
- if ((ident.following || []).includes(actor)) return true;
54
- const obj = typeof activity.object === 'object' ? activity.object : null;
55
- const audience = []
56
- .concat(activity.to || [], activity.cc || [], activity.bto || [], activity.bcc || [], activity.audience || [],
57
- obj?.to || [], obj?.cc || [], obj?.audience || [])
58
- .map(idOf).filter(Boolean);
59
- if (audience.includes(ident.actorUrl) || audience.includes(ident.followersUrl)) return true;
60
- const tag = [].concat(obj?.tag || activity.tag || []);
61
- if (tag.some(t => t?.type === 'Mention' && (t.href === ident.actorUrl || t.name?.includes(ident.actorUrl)))) return true;
62
- const inReplyTo = idOf(obj?.inReplyTo ?? activity.inReplyTo);
63
- return !!inReplyTo && ident.notesPrefix && String(inReplyTo).startsWith(ident.notesPrefix);
64
- }
65
-
66
- // Handle one inbound delivery. `request` is a WHATWG Request. `ident` is the
67
- // resolved identity policy: { inboxUrl, actorUrl, followersUrl, notesPrefix,
68
- // following, blocklist, kind, gatewayWebId, hmacSecret }. `podPut(url, body,
69
- // contentType) → boolean` appends to the pod with the gateway's credential.
70
- // `paused` says nobody is reading this inbox: content is accepted and
71
- // discarded (the same quiet 202 a blocked sender gets, so nothing retries
72
- // and nothing counts a failure against this host), control still lands.
73
- // Returns { status, reason } — the adapter turns it into an HTTP response —
74
- // and, for a delivery that reached the pod, `content` (whether it was
75
- // content rather than control) and `bytes`, so the caller can keep count.
76
- export async function handleDelivery(request, ident, { podPut, fetchImpl = fetch, maxBytes = DEFAULT_MAX_BYTES, paused = false } = {}) {
77
- // Read the body once from a clone; the original, unconsumed, goes to the
78
- // verifier (which needs the body for the Digest check).
79
- let raw;
80
- try { raw = await request.clone().text(); } catch { return { status: 400, reason: 'unreadable body' }; }
81
- if (Buffer.byteLength(raw) > maxBytes) return { status: 413, reason: 'too large' };
82
-
83
- let activity;
84
- try { activity = JSON.parse(raw); } catch { return { status: 400, reason: 'unparsable JSON' }; }
85
- const actor = idOf(activity.actor);
86
- if (!httpUrl(actor)) return { status: 400, reason: 'actor is not an http(s) URL' };
87
-
88
- // Edge drops — silent 202 so a rejected sender does not retry a delivery we
89
- // will never accept. None of these becomes a pod write.
90
- if (isBlocked(actor, ident.blocklist)) return { status: 202, reason: 'blocked' };
91
- const content = !CONTROL.has(activity.type);
92
- if (content && paused) return { status: 202, reason: 'paused' };
93
- if (content && !concernsUsAtEdge(activity, ident)) {
94
- return { status: 202, reason: 'does not concern us' };
95
- }
96
-
97
- // The sender's key, kept between deliveries: a server pushing a hundred
98
- // items is asked for its key once (caches.mjs).
99
- const v = await verifyHttpSignature(request, {
100
- documentLoader: makeSafeLoader({ fetchImpl }), keyCache: senderKeys,
101
- });
102
- // A present-but-invalid signature is a forgery — dropped here, so it never
103
- // reaches the pod (today it would, drain, and die unapplied). An absent or
104
- // unfetchable-key signature is NOT dropped: it forwards unverified and the
105
- // drain's verify-by-dereference still stands behind it.
106
- if (v.verified === false && v.reason === 'bad-signature') {
107
- return { status: 202, reason: 'forged signature' };
108
- }
109
-
110
- // No per-user secret on record = no receipt: the delivery still filters and
111
- // forwards, it just reads as unverified at the drain — never a failure.
112
- const receipt = ident.hmacSecret
113
- ? signReceipt(makeReceipt(v, { gateway: ident.gatewayWebId }), ident.hmacSecret)
114
- : null;
115
- const hash = sha256hex(raw);
116
- const okA = await inbox.appendVerifiedDelivery(podPut, ident.inboxUrl, hash, raw);
117
- // A pod-write failure returns 5xx so the ORIGIN retries over its own ladder —
118
- // that is what preserves the pod's buffer property without the gateway
119
- // holding any state.
120
- if (!okA) return { status: 502, reason: 'pod inbox write failed' };
121
- if (receipt) await inbox.writeReceiptBeside(podPut, ident.inboxUrl, hash, receipt);
122
- return { status: 202, reason: v.verified ? 'verified' : 'buffered-unverified', content, bytes: Buffer.byteLength(raw) };
123
- }
124
-
125
- // The outbox door: the owner's own post, taken on their behalf.
126
- //
127
- // A client such as dokieli POSTs an activity — or a bare object — to the
128
- // outbox address the actor advertises. The door holds no key and mints nothing:
129
- // it checks that the token proves the account's owner (the caller has already
130
- // done that and hands in `owner`), writes the bytes into the pod inbox exactly
131
- // as verified mail is written, and stamps them with a receipt whose method is
132
- // `c2s` and whose actor is this account. The drain hands such an item to the
133
- // client-to-server dispatcher, which publishes and delivers it — so the post
134
- // goes out when the agent next runs, the same way inbound mail is read.
135
- //
136
- // `slug` is the name of the new document — the client's, or one the door
137
- // chose — and `serial` and `at` name any other activity. They ride in the
138
- // receipt so the dispatcher uses them, and they are what let the door answer a
139
- // Location before anything exists. A name the client chose that is already
140
- // taken is the one case where the agent picks another.
141
- export const SLUG_OK = /^[A-Za-z0-9._-]{1,64}$/u;
142
- export const safeSlug = (s) => (typeof s === 'string' && SLUG_OK.test(s) && !/^\.+$/u.test(s) ? s : null);
143
-
144
- export async function handleOwnerPost(request, ident, { podPut, ownerWebId, maxBytes = DEFAULT_MAX_BYTES, exists = null } = {}) {
145
- if (!ident.hmacSecret) return { status: 409, reason: 'this account has no door secret — attach it again' };
146
- let raw;
147
- try { raw = await request.text(); } catch { return { status: 400, reason: 'unreadable body' }; }
148
- if (Buffer.byteLength(raw) > maxBytes) return { status: 413, reason: 'too large' };
149
- let doc;
150
- try { doc = JSON.parse(raw); } catch { return { status: 400, reason: 'unparsable JSON' }; }
151
- if (!doc || typeof doc !== 'object' || Array.isArray(doc) || !doc.type) {
152
- return { status: 400, reason: 'a typed ActivityStreams object is required' };
153
- }
154
- // A post that makes something new is named HERE, when the client did not
155
- // name it, so the answer can give the address of what it made (§6: 201 with
156
- // the new activity's id in Location) though nothing has been made yet.
157
- // What the account would refuse is refused here, before the app is told
158
- // "created" for something that will never exist.
159
- const refusal = refusedAtTheDoor(doc, ident);
160
- if (refusal) return { status: 422, reason: refusal };
161
- const makes = doc.type === 'Create' || !ACTIVITY_TYPES.has(doc.type);
162
- // A name the client asks for that is already taken is replaced here, so the
163
- // address answered is the one the post gets.
164
- let slug = safeSlug(request.headers.get('slug'));
165
- if (slug && makes && exists && await exists(containerFor(doc, ident) + slug).catch(() => false)) slug = null;
166
- if (!slug && makes) slug = mintSlug();
167
- // Anything else is named the way the agent names it, from a serial and a
168
- // time chosen here and handed over in the receipt.
169
- // Two posts in the same millisecond still get different names.
170
- const now = Date.now();
171
- const serial = now * 1000 + crypto.randomInt(1000);
172
- const at = new Date(now).toISOString();
173
- const hash = sha256hex(raw);
174
- // The receipt names the body it vouches for, so it vouches for nothing else,
175
- // and the agent can tell the same body sent twice.
176
- const receipt = signReceipt({
177
- v: 1, verified: true, method: 'c2s', keyId: ownerWebId || null, actor: ident.actorUrl,
178
- checks: ['owner-token'], reason: 'owner', gateway: ident.gatewayWebId, ...(slug ? { slug } : {}),
179
- serial, at, hash,
180
- }, ident.hmacSecret);
181
- const okA = await inbox.appendVerifiedDelivery(podPut, ident.inboxUrl, hash, raw);
182
- if (!okA) return { status: 502, reason: 'pod inbox write failed' };
183
- await inbox.writeReceiptBeside(podPut, ident.inboxUrl, hash, receipt);
184
- if (makes) {
185
- const object = slug && ident.notesPrefix ? containerFor(doc, ident) + slug : null;
186
- return { status: 201, reason: 'accepted', object, location: object ? object + '-create' : null };
187
- }
188
- const object = typeof doc.object === 'string' ? doc.object : doc.object?.id || null;
189
- return { status: 201, reason: 'accepted', object, location: activityIdFor(doc.type, object, ident, serial, at) };
190
- }
191
-
192
- // The id the agent gives each kind of activity (lib/core/wire.mjs): an edit by
193
- // its note and time, a deletion by its note, a boost and an undo as documents
194
- // in the posts folder, the rest by the actor and serial.
195
- const SERIAL_NAMED = new Set(['Like', 'Follow', 'Block', 'Add', 'Remove', 'Accept', 'Reject']);
196
- function activityIdFor(type, object, ident, serial, at) {
197
- const actor = ident.actorUrl;
198
- if ((type === 'Update' || type === 'Delete') && object && object !== actor) {
199
- return type === 'Delete' ? object + '-delete' : object + '-update-' + at.replace(/[^0-9TZ]/g, '');
200
- }
201
- if ((type === 'Announce' || type === 'Undo') && ident.notesPrefix) return `${ident.notesPrefix}${type.toLowerCase()}-${serial}`;
202
- return SERIAL_NAMED.has(type) ? `${actor}#${type.toLowerCase()}-${serial}` : null;
203
- }
204
-
205
- // The account's own changes are made on its manage page, and the one
206
- // collection a client may add to or take from is the pinned posts.
207
- function refusedAtTheDoor(doc, ident) {
208
- const object = typeof doc.object === 'string' ? doc.object : doc.object?.id || null;
209
- const target = typeof doc.target === 'string' ? doc.target : doc.target?.id || null;
210
- if (doc.type === 'Move') return 'moving the account is done on its manage page';
211
- if ((doc.type === 'Update' || doc.type === 'Delete') && object === ident.actorUrl) {
212
- return 'the account itself is changed on its manage page';
213
- }
214
- if ((doc.type === 'Add' || doc.type === 'Remove') && target !== ident.actorUrl.replace(/actor$/u, 'featured')) {
215
- return 'the pinned posts are the one collection an app may add to or take from';
216
- }
217
- return null;
218
- }
219
-
220
- // The activity types §6 names; anything else with a type is an object the
221
- // agent wraps in a Create. The same list the dispatcher keeps (lib/client/c2s.mjs).
222
- const ACTIVITY_TYPES = new Set([
223
- 'Create', 'Update', 'Delete', 'Follow', 'Like', 'Announce', 'Undo',
224
- 'Block', 'Add', 'Remove', 'Accept', 'Reject', 'Move',
225
- ]);
226
-
227
- // The name the agent would have chosen itself: the day and eight hex characters.
228
- const mintSlug = () => new Date().toISOString().slice(0, 10) + '-' + crypto.randomBytes(4).toString('hex');
229
-
230
- // Where the agent will keep it: public and unlisted posts under notes/, posts
231
- // for followers or named people under private/ — decided by the addressing,
232
- // read the way the dispatcher reads it (nothing stated is public).
233
- const PUBLIC_NAMES = new Set(['https://www.w3.org/ns/activitystreams#Public', 'as:Public', 'Public']);
234
- function containerFor(doc, ident) {
235
- const object = doc.type === 'Create' && doc.object && typeof doc.object === 'object' ? doc.object : doc;
236
- const list = (v) => (v == null ? [] : Array.isArray(v) ? v : [v]).map(x => (typeof x === 'string' ? x : x?.id));
237
- const to = list(doc.to ?? object.to);
238
- const cc = list(doc.cc ?? object.cc);
239
- const blind = list(doc.bto ?? object.bto).length + list(doc.bcc ?? object.bcc).length;
240
- const open = (!to.length && !cc.length && !blind) || [...to, ...cc].some(a => PUBLIC_NAMES.has(a));
241
- return open ? ident.notesPrefix : ident.notesPrefix.replace(/ap\/notes\/$/u, 'ap/private/');
242
- }
243
-
244
- export const _internal = { isBlocked, concernsUsAtEdge, httpUrl, sha256hex };
@@ -1,49 +0,0 @@
1
- // headers.mjs — the hardening every front response carries, in one place.
2
- // Split out of front-core.mjs, which is at its size gate; nothing here knows
3
- // about routes.
4
-
5
- // Every response this file makes, hardened in one place rather than in each of
6
- // the dozen shapes below.
7
- //
8
- // `nosniff` matters most: the front serves user-supplied JSON straight from
9
- // somebody's pod (the proxied actor and object documents), and without it a
10
- // browser is free to decide for itself that a document is HTML and run what is
11
- // inside it. The rest is the same posture the app already has — nothing may be
12
- // framed, no base tag may be rewritten, no plugin content.
13
- //
14
- // A content-security-policy goes on the HTML only: it would mean nothing on a
15
- // JSON document, and `frame-ancestors` has to be a header rather than a meta
16
- // tag anyway.
17
- //
18
- // `script-src 'self'` is the one that matters, and it is only possible because
19
- // none of these pages carries inline script any more — each has its own file and
20
- // its own route above. A policy cannot tell an inline block the author wrote
21
- // from one an attacker injected, so as long as any inline script has to run,
22
- // every inline script may.
23
- //
24
- // `connect-src` allows https: because the pages sign in against the user's own
25
- // pod, which is a different origin by definition and not one we can name here.
26
- export function withSecurityHeaders(headers = {}, body = null) {
27
- const ct = String(headers['content-type'] || '');
28
- const isHtml = ct.startsWith('text/html');
29
- return {
30
- ...headers,
31
- 'x-content-type-options': 'nosniff',
32
- 'referrer-policy': 'same-origin',
33
- 'x-frame-options': 'SAMEORIGIN',
34
- ...(isHtml && body ? {
35
- 'content-security-policy': [
36
- "default-src 'self'",
37
- "script-src 'self'", // no inline script: see above
38
- "style-src 'self' 'unsafe-inline'",
39
- "img-src 'self' https: data:",
40
- "connect-src 'self' https:", // sign-in goes to the user's own pod
41
- "object-src 'none'", // no plugin content, ever
42
- "base-uri 'none'", // no rewriting where relative URLs resolve
43
- "frame-ancestors 'self'", // nobody else may frame these pages
44
- "form-action 'self'", // a form here submits here
45
- ].join('; '),
46
- } : {}),
47
- };
48
- }
49
-
@@ -1,159 +0,0 @@
1
- // httpsig.mjs — inbound HTTP-Signature verification, and the verification
2
- // receipt that carries the outcome from wherever the verify happened (an
3
- // always-on gateway, or the agent's own endpoint when it is publicly reachable)
4
- // into the drain, which cannot see the delivery's headers.
5
- //
6
- // We SIGN outbound with Fedify draft-cavage (lib/deliver.mjs); we VERIFY with
7
- // the same library, so the signing-string reconstruction can never drift out
8
- // of step with what we produce. The one thing Fedify must be prevented from
9
- // doing on its own is fetching an attacker-named key over an unguarded socket
10
- // — so the key deref is injected as a loader that goes through safefetch.
11
-
12
- import crypto from 'node:crypto';
13
- import { verifyRequestDetailed } from '@fedify/fedify/sig';
14
- import { assertPublicUrl, safeFetch, readCapped } from '../shared/safefetch.mjs';
15
-
16
- // An SSRF-safe JSON-LD document loader for Fedify's key fetch. The keyId in a
17
- // delivery is chosen by the sender, so every deref it triggers is untrusted
18
- // input and must pass the same address policy as the rest of our outbound
19
- // path. `skipActor` forces a network read past any cache, for the one refetch
20
- // a rotated key needs.
21
- export function makeSafeLoader({ getActors = null, fetchImpl = fetch } = {}) {
22
- return async (url) => {
23
- // A cached actor document short-circuits the network entirely — and it was
24
- // only cached after its own origin vouched for its id (intake.fetchAP).
25
- if (getActors) {
26
- const hit = getActors()[url];
27
- if (hit) return { document: hit, documentUrl: url, contextUrl: null };
28
- }
29
- await assertPublicUrl(url);
30
- const res = await safeFetch(url, {
31
- headers: { accept: 'application/activity+json, application/ld+json' },
32
- }, fetchImpl);
33
- if (res.status >= 400) throw new Error(`key fetch ${url} → ${res.status}`);
34
- const document = JSON.parse(await readCapped(res));
35
- return { document, documentUrl: url, contextUrl: null };
36
- };
37
- }
38
-
39
- // Verify a delivery's signature. `request` is a WHATWG Request (the gateway
40
- // has one natively; an agent-side endpoint builds one from its req). Returns a
41
- // normalized result both the gateway and the drain understand — never throws
42
- // on a bad signature, because "unverified" is a routine outcome, not an error.
43
- //
44
- // Three outcomes, kept apart because the door acts on them differently. Only a
45
- // signature we could CHECK and found wrong is a forgery. A key we could not
46
- // fetch proves nothing: a sender in secure mode — Threads, Mastodon with
47
- // AUTHORIZED_FETCH — answers this keyless loader 404 or 401, so its deliveries
48
- // have to degrade to unverified rather than be read as forged.
49
- export async function verifyHttpSignature(request, { documentLoader, keyCache, timeWindow } = {}) {
50
- const hadSig = request.headers.get('signature') != null;
51
- let result = null;
52
- try {
53
- result = await verifyRequestDetailed(request, {
54
- documentLoader,
55
- ...(keyCache ? { keyCache } : {}),
56
- timeWindow: timeWindow ?? { hours: 1 },
57
- });
58
- } catch {
59
- result = null; // the library itself failed — no more informative than a bad signature
60
- }
61
- // Which signature the sender used: RFC 9421 carries a Signature-Input
62
- // header beside Signature; the cavage draft carries Signature alone. The
63
- // library picks the verifier by the same header.
64
- const method = request.headers.has('signature-input') ? 'rfc9421' : 'draft-cavage';
65
- if (result?.verified) {
66
- const key = result.key;
67
- return {
68
- verified: true, method,
69
- keyId: key.id?.href ?? null, actor: key.ownerId?.href ?? null, reason: null,
70
- checks: { signature: true, digest: true, dateSkew: true, keyFetched: true },
71
- };
72
- }
73
- const kind = result?.reason?.type;
74
- if (!hadSig || kind === 'noSignature') {
75
- return {
76
- verified: false, method: 'none', keyId: null, actor: null,
77
- reason: 'no-signature', checks: { signature: false },
78
- };
79
- }
80
- if (kind === 'keyFetchError') {
81
- return {
82
- verified: false, method, keyId: null, actor: null,
83
- reason: 'key-unfetchable', checks: { signature: false, keyFetched: false },
84
- };
85
- }
86
- return {
87
- verified: false, method, keyId: null, actor: null,
88
- reason: 'bad-signature', checks: { signature: false },
89
- };
90
- }
91
-
92
- // The receipt: what the door concluded, HMAC-stamped so the drain can trust it
93
- // even while the inbox is still public-Append (a forger without the secret
94
- // cannot mint `verified: true`). The HMAC is over the receipt minus the hmac
95
- // field itself, with sorted keys so gateway and drain canonicalize alike.
96
- //
97
- // JSON.stringify's replacer ARRAY applies at EVERY level, not just the top: the
98
- // old canonical() passed the top-level key names, so `checks` — whose own keys
99
- // are `signature`, `digest`, `dateSkew`, `keyFetched` — serialized as `{}` and
100
- // was covered by no signature at all. Anything nested was equally unstamped.
101
- // Sort recursively instead, and exclude `hmac` only where it actually lives.
102
- function sortedKeys(v) {
103
- if (Array.isArray(v)) return v.map(sortedKeys);
104
- if (v && typeof v === 'object') {
105
- const out = {};
106
- for (const k of Object.keys(v).sort()) out[k] = sortedKeys(v[k]);
107
- return out;
108
- }
109
- return v;
110
- }
111
- function canonical(obj) {
112
- const { hmac, ...rest } = obj; // eslint-disable-line no-unused-vars
113
- return JSON.stringify(sortedKeys(rest));
114
- }
115
-
116
- // What canonical() used to produce. Kept ONLY for verification, and only until
117
- // every door has been redeployed: a gateway (fedipod.net) and an agent update
118
- // independently, so for a while a receipt signed by an old door has to keep
119
- // verifying against a new drain. Receipts are short-lived — one per inbox item,
120
- // deleted with the item — so this can go once the doors have turned over.
121
- // Nothing signs with it.
122
- function canonicalLegacy(obj) {
123
- return JSON.stringify(obj, Object.keys(obj).filter(k => k !== 'hmac').sort());
124
- }
125
-
126
- export function makeReceipt(result, { gateway = null } = {}) {
127
- return {
128
- v: 1,
129
- verified: !!result.verified,
130
- method: result.method,
131
- keyId: result.keyId,
132
- actor: result.actor,
133
- checks: result.checks,
134
- reason: result.reason,
135
- gateway,
136
- };
137
- }
138
-
139
- export function signReceipt(receipt, secret) {
140
- const hmac = crypto.createHmac('sha256', secret).update(canonical(receipt)).digest('base64');
141
- return { ...receipt, hmac };
142
- }
143
-
144
- // True only when the receipt carries a valid HMAC for `secret`. A missing
145
- // secret, a missing hmac, or a mismatch all read as "not ours" → the drain
146
- // treats the receipt as absent (today's unverified behavior). timingSafeEqual
147
- // over equal-length buffers; unequal lengths are a straight mismatch.
148
- export function verifyReceipt(receipt, secret) {
149
- if (!receipt || typeof receipt !== 'object' || !receipt.hmac || !secret) return false;
150
- let given;
151
- try { given = Buffer.from(receipt.hmac, 'base64'); } catch { return false; }
152
- const matches = (text) => {
153
- const expected = crypto.createHmac('sha256', secret).update(text).digest();
154
- return given.length === expected.length && crypto.timingSafeEqual(given, expected);
155
- };
156
- // The current stamp first; the pre-2026-09-09 one second, for receipts a door
157
- // that has not been redeployed yet is still writing. See canonicalLegacy.
158
- return matches(canonical(receipt)) || matches(canonicalLegacy(receipt));
159
- }
@@ -1,84 +0,0 @@
1
- // notices.mjs — notices from whoever runs the front, to every account here.
2
- //
3
- // The operator writes them on the /notices page, proved as the admin the way
4
- // the roster is; every page of ours asks GET /api/notices once per load and
5
- // shows a bell when there are any. A notice is a title, a plain-text body and
6
- // a time. The body is never markup: the pages render it as text.
7
- //
8
- // Kept beside the directory, in a store the adapter supplies:
9
- // listNotices() -> { id: notice } putNotice(id, notice) deleteNotice(id)
10
- // A front that supplies none has no notices to offer, and says so.
11
-
12
- import crypto from 'node:crypto';
13
-
14
- const TITLE_MAX = 120;
15
- const BODY_MAX = 4000;
16
- const NOTICES_EDGE_SECONDS = 60;
17
-
18
- const clean = (v, max) => String(v ?? '').replace(/\r\n?/gu, '\n').trim().slice(0, max);
19
- const newest = (a, b) => String(b.at || '').localeCompare(String(a.at || ''));
20
-
21
- export async function listNotices(ctx) {
22
- const all = ctx.listNotices ? await ctx.listNotices() : {};
23
- return Object.values(all || {}).filter(Boolean).sort(newest);
24
- }
25
-
26
- // GET is public: what every account is shown. POST is the admin's alone. The
27
- // /notices page is where the admin writes them; it signs in like the roster.
28
- // Returns a response, or null when the path is neither.
29
- export async function routeNoticesApi(request, pathname, ctx, { j, verifyPodToken, publicFor, notFound }) {
30
- // The two admin pages: the roster (who has accounts here) and the notices.
31
- // Each signs in and calls its API; a deploy with no admin supplies no page.
32
- const page = pathname === '/notices' ? ctx.noticesPage : pathname === '/roster' ? ctx.adminPage : undefined;
33
- if (page !== undefined) {
34
- if (request.method !== 'GET' && request.method !== 'HEAD') return { status: 405, headers: {}, body: '' };
35
- if (!page) return notFound();
36
- return { status: 200, headers: { 'content-type': 'text/html; charset=utf-8' }, body: page };
37
- }
38
- if (pathname !== '/api/notices') return null;
39
- const open = { 'access-control-allow-origin': '*' };
40
- if (request.method === 'OPTIONS') {
41
- return { status: 204, headers: { ...open, 'access-control-allow-methods': 'GET, OPTIONS',
42
- 'access-control-allow-headers': 'Accept', allow: 'GET, POST, OPTIONS' }, body: null };
43
- }
44
- if (request.method === 'GET') {
45
- if (!ctx.listNotices) return j(501, { error: 'this front keeps no notices' });
46
- const out = j(200, { notices: await listNotices(ctx) });
47
- Object.assign(out.headers, open, publicFor(NOTICES_EDGE_SECONDS));
48
- return out;
49
- }
50
- if (request.method !== 'POST') return { status: 405, headers: {}, body: '' };
51
- if (!ctx.putNotice || !ctx.deleteNotice || !ctx.adminWebId) return j(501, { error: 'this front keeps no notices' });
52
- const webid = await verifyPodToken(request, pathname, ctx.verifier);
53
- if (!webid) return j(401, { error: 'a Solid-OIDC token is required' });
54
- if (webid !== ctx.adminWebId) return j(403, { error: 'that WebID is not the admin of this front' });
55
- let body;
56
- try { body = JSON.parse(await request.clone().text()); } catch { return j(400, { error: 'bad JSON' }); }
57
- const action = String(body.action || '');
58
- const all = (ctx.listNotices ? await ctx.listNotices() : {}) || {};
59
- if (action === 'delete') {
60
- const id = String(body.id || '');
61
- if (!all[id]) return j(404, { error: 'no such notice' });
62
- await ctx.deleteNotice(id);
63
- console.log(`front: notice ${id} deleted by the admin`);
64
- return j(200, { ok: true, id, deleted: true });
65
- }
66
- if (action !== 'create' && action !== 'update') return j(400, { error: 'action must be create, update or delete' });
67
- const title = clean(body.title, TITLE_MAX);
68
- const text = clean(body.body, BODY_MAX);
69
- if (!title) return j(400, { error: 'a title is required' });
70
- if (!text) return j(400, { error: 'a body is required' });
71
- const now = new Date().toISOString();
72
- let notice;
73
- if (action === 'create') {
74
- const id = `${Date.now().toString(36)}-${crypto.randomBytes(4).toString('hex')}`;
75
- notice = { id, title, body: text, at: now, updatedAt: now };
76
- } else {
77
- const prior = all[String(body.id || '')];
78
- if (!prior) return j(404, { error: 'no such notice' });
79
- notice = { ...prior, title, body: text, updatedAt: now };
80
- }
81
- await ctx.putNotice(notice.id, notice);
82
- console.log(`front: notice ${notice.id} ${action}d by the admin`);
83
- return j(action === 'create' ? 201 : 200, { ok: true, notice });
84
- }