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.
- package/README.md +38 -34
- package/config/server.json +22 -0
- package/dist/claims.js +3 -2
- package/dist/directory.d.ts +2 -0
- package/dist/door.d.ts +4 -0
- package/dist/door.js +31 -0
- package/dist/fedipod.d.ts +16 -0
- package/dist/fedipod.js +13 -0
- package/dist/front.d.ts +3 -0
- package/dist/front.js +94 -0
- package/dist/handler.d.ts +39 -101
- package/dist/handler.js +86 -617
- package/dist/handler.jsonld +6 -30
- package/dist/identities.d.ts +62 -0
- package/dist/identities.js +236 -0
- package/dist/mounts.d.ts +43 -0
- package/dist/mounts.js +123 -0
- package/dist/opt-in.d.ts +46 -0
- package/dist/opt-in.js +176 -0
- package/dist/store-pod.js +37 -0
- package/package.json +10 -18
- package/lib/client/c2s.mjs +0 -582
- package/lib/client/localapi.mjs +0 -87
- package/lib/client/masto/accounts.mjs +0 -309
- package/lib/client/masto/body.mjs +0 -156
- package/lib/client/masto/index.mjs +0 -206
- package/lib/client/masto/instance.mjs +0 -112
- package/lib/client/masto/media.mjs +0 -79
- package/lib/client/masto/oauth.mjs +0 -656
- package/lib/client/masto/render.mjs +0 -518
- package/lib/client/masto/statuses.mjs +0 -372
- package/lib/client/masto/timelines.mjs +0 -438
- package/lib/client/oidc-auth.mjs +0 -62
- package/lib/client/streaming.mjs +0 -154
- package/lib/client/webpush.mjs +0 -98
- package/lib/connections/acctfeed.mjs +0 -257
- package/lib/connections/atproto.mjs +0 -309
- package/lib/connections/bskyfeed.mjs +0 -243
- package/lib/connections/bskygroup.mjs +0 -162
- package/lib/connections/fediacct.mjs +0 -253
- package/lib/connections/import.mjs +0 -357
- package/lib/connections/tagfeed.mjs +0 -212
- package/lib/connections/vault.mjs +0 -114
- package/lib/core/as2.mjs +0 -206
- package/lib/core/contexts/activitystreams.json +0 -379
- package/lib/core/contexts/anno.json +0 -126
- package/lib/core/contexts/did-v1.json +0 -57
- package/lib/core/contexts/fep-5711.json +0 -36
- package/lib/core/contexts/gotosocial.json +0 -86
- package/lib/core/contexts/identity-v1.json +0 -152
- package/lib/core/contexts/index.mjs +0 -63
- package/lib/core/contexts/join-lemmy.json +0 -33
- package/lib/core/contexts/joinmastodon.json +0 -28
- package/lib/core/contexts/litepub-0.1.json +0 -60
- package/lib/core/contexts/map.json +0 -19
- package/lib/core/contexts/miscellany.json +0 -19
- package/lib/core/contexts/quotes.json +0 -18
- package/lib/core/contexts/schemaorg.json +0 -8845
- package/lib/core/contexts/security-data-integrity-v1.json +0 -78
- package/lib/core/contexts/security-data-integrity-v2.json +0 -81
- package/lib/core/contexts/security-multikey-v1.json +0 -35
- package/lib/core/contexts/security-v1.json +0 -74
- package/lib/core/contexts/webfinger.json +0 -10
- package/lib/core/deliver.mjs +0 -371
- package/lib/core/graphview.mjs +0 -327
- package/lib/core/intake/activities.mjs +0 -633
- package/lib/core/intake/activity.mjs +0 -318
- package/lib/core/intake/channel.mjs +0 -144
- package/lib/core/intake/group.mjs +0 -256
- package/lib/core/intake/index.mjs +0 -701
- package/lib/core/intake/notes.mjs +0 -360
- package/lib/core/intake/verify.mjs +0 -142
- package/lib/core/keys.mjs +0 -153
- package/lib/core/lease.mjs +0 -206
- package/lib/core/polls.mjs +0 -105
- package/lib/core/profile-page.mjs +0 -104
- package/lib/core/proof.mjs +0 -79
- package/lib/core/publisher/collections.mjs +0 -237
- package/lib/core/publisher/index.mjs +0 -500
- package/lib/core/publisher/notes.mjs +0 -453
- package/lib/core/publisher/questions.mjs +0 -239
- package/lib/core/publisher/restore.mjs +0 -224
- package/lib/core/shapes/activitystreams.ttl +0 -157
- package/lib/core/shapes/index.mjs +0 -132
- package/lib/core/shapes/shapes-text.mjs +0 -13
- package/lib/core/social.mjs +0 -531
- package/lib/core/storage.mjs +0 -176
- package/lib/core/store.mjs +0 -568
- package/lib/core/wire-quotes.mjs +0 -71
- package/lib/core/wire.mjs +0 -889
- package/lib/device/account.mjs +0 -87
- package/lib/device/admin/index.mjs +0 -13
- package/lib/device/admin/origins.mjs +0 -35
- package/lib/device/admin/routes/connections.mjs +0 -144
- package/lib/device/admin/routes/gateway.mjs +0 -199
- package/lib/device/admin/routes/lifecycle.mjs +0 -200
- package/lib/device/admin/routes/owner.mjs +0 -322
- package/lib/device/admin/routes/setup.mjs +0 -412
- package/lib/device/admin/routes/social.mjs +0 -188
- package/lib/device/admin/server.mjs +0 -95
- package/lib/device/admin/static.mjs +0 -247
- package/lib/device/admin/surface.mjs +0 -315
- package/lib/device/certs.mjs +0 -281
- package/lib/device/cli/commands/account.mjs +0 -586
- package/lib/device/cli/commands/run.mjs +0 -278
- package/lib/device/cli/commands/service.mjs +0 -221
- package/lib/device/cli/commands/setup.mjs +0 -447
- package/lib/device/cli/commands/state.mjs +0 -561
- package/lib/device/cli/context.mjs +0 -288
- package/lib/device/export-collections.mjs +0 -161
- package/lib/device/gateway-move.mjs +0 -58
- package/lib/device/home.mjs +0 -161
- package/lib/device/migrate.mjs +0 -115
- package/lib/device/ports.mjs +0 -27
- package/lib/device/remote.mjs +0 -54
- package/lib/device/setup.mjs +0 -459
- package/lib/device/update.mjs +0 -96
- package/lib/gateway/caches.mjs +0 -36
- package/lib/gateway/directory.mjs +0 -90
- package/lib/gateway/front-core.mjs +0 -970
- package/lib/gateway/gateway-core.mjs +0 -165
- package/lib/gateway/headers.mjs +0 -49
- package/lib/gateway/httpsig.mjs +0 -159
- package/lib/gateway/notices.mjs +0 -84
- package/lib/gateway/quiet.mjs +0 -189
- package/lib/pod/README.md +0 -61
- package/lib/pod/actor.mjs +0 -96
- package/lib/pod/collection.mjs +0 -77
- package/lib/pod/containers.mjs +0 -140
- package/lib/pod/discovery.mjs +0 -46
- package/lib/pod/featured.mjs +0 -18
- package/lib/pod/followers.mjs +0 -24
- package/lib/pod/following.mjs +0 -10
- package/lib/pod/http.mjs +0 -74
- package/lib/pod/inbox.mjs +0 -166
- package/lib/pod/links.mjs +0 -35
- package/lib/pod/media.mjs +0 -27
- package/lib/pod/notes.mjs +0 -80
- package/lib/pod/notifications.mjs +0 -89
- package/lib/pod/outbox.mjs +0 -18
- package/lib/pod/package.json +0 -19
- package/lib/pod/policy.mjs +0 -58
- package/lib/pod/private.mjs +0 -19
- package/lib/pod/root.mjs +0 -104
- package/lib/pod/state.mjs +0 -40
- package/lib/pod/transport.mjs +0 -525
- package/lib/pod/urls.mjs +0 -85
- package/lib/server/embed.mjs +0 -423
- package/lib/session/README.md +0 -137
- package/lib/session/demo.html +0 -94
- package/lib/session/fedi-account.mjs +0 -408
- package/lib/session/fedi-login.mjs +0 -123
- package/lib/session/oidc-session.mjs +0 -233
- package/lib/session/package.json +0 -49
- package/lib/shared/guard.mjs +0 -213
- package/lib/shared/links.mjs +0 -9
- package/lib/shared/safefetch.mjs +0 -235
- package/lib/shared/ua.mjs +0 -29
- package/phanpy/dist/404.html +0 -40
- package/phanpy/dist/_headers +0 -2
- package/phanpy/dist/apple-touch-icon.png +0 -0
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js +0 -2
- package/phanpy/dist/assets/ICONS-DIPJeU0C.js.map +0 -1
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js +0 -183
- package/phanpy/dist/assets/ScheduledAtField-D-Abfo4J.js.map +0 -1
- package/phanpy/dist/assets/Temml-CCjoacWt.woff2 +0 -0
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js +0 -2
- package/phanpy/dist/assets/__vite-browser-external-C7iut881.js.map +0 -1
- package/phanpy/dist/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/phanpy/dist/assets/catch-up-O5q5C75Z.png +0 -0
- package/phanpy/dist/assets/chunk-aKtaBQYM.js +0 -1
- package/phanpy/dist/assets/compose-CFEx7wb4.js +0 -2
- package/phanpy/dist/assets/compose-CFEx7wb4.js.map +0 -1
- package/phanpy/dist/assets/compose-ZJN5xSb4.js +0 -35
- package/phanpy/dist/assets/compose-ZJN5xSb4.js.map +0 -1
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js +0 -52
- package/phanpy/dist/assets/debug.module-YFQgwnIr.js.map +0 -1
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js +0 -2
- package/phanpy/dist/assets/dom-p1YcJ5Rw.js.map +0 -1
- package/phanpy/dist/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/phanpy/dist/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/phanpy/dist/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/phanpy/dist/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/phanpy/dist/assets/icons/add-circle-line-WD0mwORD.js +0 -1
- package/phanpy/dist/assets/icons/alert-line-CpFUMf9s.js +0 -1
- package/phanpy/dist/assets/icons/android-2-line-Bzxnrd3S.js +0 -1
- package/phanpy/dist/assets/icons/announcement-line-5nwaJFJm.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +0 -1
- package/phanpy/dist/assets/icons/arrow-down-line-B8uzXzq6.js +0 -1
- package/phanpy/dist/assets/icons/arrow-left-line-CGrGT9C-.js +0 -1
- package/phanpy/dist/assets/icons/arrow-right-line-CE-HERfX.js +0 -1
- package/phanpy/dist/assets/icons/arrow-to-up-line-CvdQjmcy.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-circle-line-DEparSNF.js +0 -1
- package/phanpy/dist/assets/icons/arrow-up-line-C7yBeugc.js +0 -1
- package/phanpy/dist/assets/icons/arrows-right-line-BtbXmJGP.js +0 -1
- package/phanpy/dist/assets/icons/at-line-CF66Bznk.js +0 -1
- package/phanpy/dist/assets/icons/attachment-line-BY8It-l9.js +0 -1
- package/phanpy/dist/assets/icons/blockquote-line-BS6pU_hm.js +0 -1
- package/phanpy/dist/assets/icons/board-line-C0UWXyA6.js +0 -1
- package/phanpy/dist/assets/icons/bookmark-line-1CtX5JLF.js +0 -1
- package/phanpy/dist/assets/icons/building-5-line-Dd59MzbO.js +0 -1
- package/phanpy/dist/assets/icons/bus-2-line-6_dvA0GG.js +0 -1
- package/phanpy/dist/assets/icons/calendar-day-line-Crpl_fmu.js +0 -1
- package/phanpy/dist/assets/icons/calendar-month-line-vommts2C.js +0 -1
- package/phanpy/dist/assets/icons/calendar-time-add-line-BU_DTlQk.js +0 -1
- package/phanpy/dist/assets/icons/camera-line-Cv69MEei.js +0 -1
- package/phanpy/dist/assets/icons/celebrate-line-B8DvIoi0.js +0 -1
- package/phanpy/dist/assets/icons/chart-bar-line-FK-QOmtF.js +0 -1
- package/phanpy/dist/assets/icons/chart-line-line-BGcJAFWw.js +0 -1
- package/phanpy/dist/assets/icons/chat-3-line-YFK2-hwz.js +0 -1
- package/phanpy/dist/assets/icons/check-circle-line-DsAxFr5-.js +0 -1
- package/phanpy/dist/assets/icons/clipboard-line-BBp364gJ.js +0 -1
- package/phanpy/dist/assets/icons/close-circle-line-CICdiuGz.js +0 -1
- package/phanpy/dist/assets/icons/close-line-DYrFWcjv.js +0 -1
- package/phanpy/dist/assets/icons/cloud-line-tiQAbah8.js +0 -1
- package/phanpy/dist/assets/icons/code-line-fAl8iiV3.js +0 -1
- package/phanpy/dist/assets/icons/comment-2-line-CR3g7WeB.js +0 -1
- package/phanpy/dist/assets/icons/copy-2-line-CLW69yF5.js +0 -1
- package/phanpy/dist/assets/icons/delete-2-line-B8xmbr4n.js +0 -1
- package/phanpy/dist/assets/icons/document-3-line-DBGNhAg2.js +0 -1
- package/phanpy/dist/assets/icons/document-line-C7mb9sb7.js +0 -1
- package/phanpy/dist/assets/icons/down-line-BGqSSThd.js +0 -1
- package/phanpy/dist/assets/icons/edit-4-line-DcgGaEhx.js +0 -1
- package/phanpy/dist/assets/icons/emoji-2-line-D8YgnM-y.js +0 -1
- package/phanpy/dist/assets/icons/exit-line-Dc07BS-x.js +0 -1
- package/phanpy/dist/assets/icons/external-link-line-BvpSkmjO.js +0 -1
- package/phanpy/dist/assets/icons/eye-2-line-CiWXx-f_.js +0 -1
- package/phanpy/dist/assets/icons/eye-close-line-BgTaSJ4P.js +0 -1
- package/phanpy/dist/assets/icons/filter-2-line-B_mR2usy.js +0 -1
- package/phanpy/dist/assets/icons/filter-line-C_nm-3cU.js +0 -1
- package/phanpy/dist/assets/icons/flag-1-line-C2HB05I0.js +0 -1
- package/phanpy/dist/assets/icons/forbid-circle-line-D3LTT9yd.js +0 -1
- package/phanpy/dist/assets/icons/formula-line-DLrt2d6j.js +0 -1
- package/phanpy/dist/assets/icons/grid-line-CgUmEikf.js +0 -1
- package/phanpy/dist/assets/icons/group-line-C5fnX0wU.js +0 -1
- package/phanpy/dist/assets/icons/hand-finger-2-line-BGgxbuL9.js +0 -1
- package/phanpy/dist/assets/icons/hashtag-line-CyTEEXBb.js +0 -1
- package/phanpy/dist/assets/icons/heart-crack-line-BZddbO2C.js +0 -1
- package/phanpy/dist/assets/icons/heart-line-CJ7K47J4.js +0 -1
- package/phanpy/dist/assets/icons/history-2-line-kWWvFzqv.js +0 -1
- package/phanpy/dist/assets/icons/history-line-bGQiwbTH.js +0 -1
- package/phanpy/dist/assets/icons/home-3-line-fRU1zeLG.js +0 -1
- package/phanpy/dist/assets/icons/information-line-iNk6-sOY.js +0 -1
- package/phanpy/dist/assets/icons/keyboard-line-DsCyuQNz.js +0 -1
- package/phanpy/dist/assets/icons/layout-4-line-CZkOX52Z.js +0 -1
- package/phanpy/dist/assets/icons/layout-5-line-B_znt5wO.js +0 -1
- package/phanpy/dist/assets/icons/left-line-eUw0tya4.js +0 -1
- package/phanpy/dist/assets/icons/lightning-line-DaKoDdix.js +0 -1
- package/phanpy/dist/assets/icons/link-2-line-DCqLmMA7.js +0 -1
- package/phanpy/dist/assets/icons/list-check-line-BAxosBh9.js +0 -1
- package/phanpy/dist/assets/icons/lock-line-CPoaXeUW.js +0 -1
- package/phanpy/dist/assets/icons/mail-line-B0P8aa1L.js +0 -1
- package/phanpy/dist/assets/icons/moon-line-CEqNAToP.js +0 -1
- package/phanpy/dist/assets/icons/more-1-fill-Dw2e5xab.js +0 -1
- package/phanpy/dist/assets/icons/more-3-line-BIiVjYTz.js +0 -1
- package/phanpy/dist/assets/icons/notification-line-BZZq2Gtu.js +0 -1
- package/phanpy/dist/assets/icons/pencil-line-CKh8-A1v.js +0 -1
- package/phanpy/dist/assets/icons/photo-album-line-Bnfg2u40.js +0 -1
- package/phanpy/dist/assets/icons/pin-line-DpTolyhs.js +0 -1
- package/phanpy/dist/assets/icons/play-fill-B0-G_8lX.js +0 -1
- package/phanpy/dist/assets/icons/qrcode-2-line-B6SPCaHn.js +0 -1
- package/phanpy/dist/assets/icons/quill-pen-line-B6LUrnCP.js +0 -1
- package/phanpy/dist/assets/icons/quote-left-fill-hQZ1cj_6.js +0 -1
- package/phanpy/dist/assets/icons/radar-line-CKyroTth.js +0 -1
- package/phanpy/dist/assets/icons/react-line-CoIKhhtK.js +0 -1
- package/phanpy/dist/assets/icons/refresh-2-line-D0mTF97S.js +0 -1
- package/phanpy/dist/assets/icons/right-line-B3KWJaqc.js +0 -1
- package/phanpy/dist/assets/icons/rocket-line-4KZl537D.js +0 -1
- package/phanpy/dist/assets/icons/round-fill-C69NTrZ0.js +0 -1
- package/phanpy/dist/assets/icons/round-line-B9k_1uo-.js +0 -1
- package/phanpy/dist/assets/icons/route-line-B28ReHcE.js +0 -1
- package/phanpy/dist/assets/icons/rows-4-line-CrBniagM.js +0 -1
- package/phanpy/dist/assets/icons/scan-line-DSf74YdP.js +0 -1
- package/phanpy/dist/assets/icons/search-2-line-B6geIU1z.js +0 -1
- package/phanpy/dist/assets/icons/settings-3-line-azJwK47w.js +0 -1
- package/phanpy/dist/assets/icons/settings-6-line-C_5zc5YR.js +0 -1
- package/phanpy/dist/assets/icons/share-2-line-D5MxWjrm.js +0 -1
- package/phanpy/dist/assets/icons/share-forward-line-Cp61fZzZ.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-2-line-DYYyXTaW.js +0 -1
- package/phanpy/dist/assets/icons/sparkles-line-C2hY1s6I.js +0 -1
- package/phanpy/dist/assets/icons/time-line-BF3eet1v.js +0 -1
- package/phanpy/dist/assets/icons/transfer-4-line-oisfvTBw.js +0 -1
- package/phanpy/dist/assets/icons/translate-line-BFWol2Ce.js +0 -1
- package/phanpy/dist/assets/icons/unlock-line-X_x5vwv3.js +0 -1
- package/phanpy/dist/assets/icons/upload-3-line-DgzwUJeW.js +0 -1
- package/phanpy/dist/assets/icons/user-4-line-BVSynZCp.js +0 -1
- package/phanpy/dist/assets/icons/user-add-2-line-BlGS5Q8q.js +0 -1
- package/phanpy/dist/assets/icons/user-add-line-BbXdHG4z.js +0 -1
- package/phanpy/dist/assets/icons/user-edit-line-BFAHOV0X.js +0 -1
- package/phanpy/dist/assets/icons/user-follow-line-BXux0wSd.js +0 -1
- package/phanpy/dist/assets/icons/user-star-line-DdYlpHLU.js +0 -1
- package/phanpy/dist/assets/icons/user-warning-line-Cj43j0_E.js +0 -1
- package/phanpy/dist/assets/icons/user-x-line-DIDgNlPH.js +0 -1
- package/phanpy/dist/assets/icons/volume-line-Bs390KQt.js +0 -1
- package/phanpy/dist/assets/icons/volume-mute-line-DzlzIqFZ.js +0 -1
- package/phanpy/dist/assets/icons/walk-line-yNXfB-md.js +0 -1
- package/phanpy/dist/assets/icons/world-2-line-CenkGIwj.js +0 -1
- package/phanpy/dist/assets/icons/zoom-in-line-CaENUphf.js +0 -1
- package/phanpy/dist/assets/icons/zoom-out-line-BDNk0EWo.js +0 -1
- package/phanpy/dist/assets/instances-BNPptnn-.json +0 -333
- package/phanpy/dist/assets/locales/ar-SA-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/ca-ES-DHRW86Tz.js +0 -1
- package/phanpy/dist/assets/locales/cs-CZ-CKoqEuA1.js +0 -1
- package/phanpy/dist/assets/locales/de-DE-ByRPMRFy.js +0 -1
- package/phanpy/dist/assets/locales/eo-UY-CPC7wLCS.js +0 -1
- package/phanpy/dist/assets/locales/es-ES-dJa0Fd9t.js +0 -1
- package/phanpy/dist/assets/locales/eu-ES-C3djlPR2.js +0 -1
- package/phanpy/dist/assets/locales/fa-IR-DTT490Ck.js +0 -1
- package/phanpy/dist/assets/locales/fi-FI-B23MCPuz.js +0 -1
- package/phanpy/dist/assets/locales/fr-FR-BcaQsO5C.js +0 -1
- package/phanpy/dist/assets/locales/gl-ES-BR-CVKBN.js +0 -1
- package/phanpy/dist/assets/locales/he-IL-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/hu-HU-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/it-IT-C1136UFV.js +0 -1
- package/phanpy/dist/assets/locales/ja-JP-CRZmRAhD.js +0 -1
- package/phanpy/dist/assets/locales/kab-Cz8xH_TT.js +0 -1
- package/phanpy/dist/assets/locales/ko-KR-CG9UiY4o.js +0 -1
- package/phanpy/dist/assets/locales/lt-LT-CJcI6sHZ.js +0 -1
- package/phanpy/dist/assets/locales/nb-NO-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/nl-NL-DxN4Yc1t.js +0 -1
- package/phanpy/dist/assets/locales/oc-FR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/pl-PL-DkwGXf6P.js +0 -1
- package/phanpy/dist/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +0 -1
- package/phanpy/dist/assets/locales/pt-BR-nGL0HdFS.js +0 -1
- package/phanpy/dist/assets/locales/pt-PT-Cp17aj2z.js +0 -1
- package/phanpy/dist/assets/locales/ru-RU-0Y8cIoaK.js +0 -1
- package/phanpy/dist/assets/locales/th-TH-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tok-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/tr-TR-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/locales/uk-UA-C5E3dFBf.js +0 -1
- package/phanpy/dist/assets/locales/zh-CN-BHJi7_et.js +0 -1
- package/phanpy/dist/assets/locales/zh-TW-CfheRbCu.js +0 -1
- package/phanpy/dist/assets/main-BdqNbG-a.js +0 -26
- package/phanpy/dist/assets/main-BdqNbG-a.js.map +0 -1
- package/phanpy/dist/assets/mock-home-DGhR__He.js +0 -2
- package/phanpy/dist/assets/mock-home-DGhR__He.js.map +0 -1
- package/phanpy/dist/assets/multi-column-ROck0NVt.jpg +0 -0
- package/phanpy/dist/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/phanpy/dist/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js +0 -2
- package/phanpy/dist/assets/polyfill-force-H8sUkLGp.js.map +0 -1
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js +0 -20
- package/phanpy/dist/assets/pwa-viewport-ogKbF51K.js.map +0 -1
- package/phanpy/dist/assets/style-B1iLPTPR.css +0 -2
- package/phanpy/dist/assets/temml-DuyIQMei.js +0 -97
- package/phanpy/dist/assets/temml-DuyIQMei.js.map +0 -1
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js +0 -2
- package/phanpy/dist/assets/tinyld.light.node-DtbBhLYC.js.map +0 -1
- package/phanpy/dist/assets/year-in-posts-B2lywqdx.png +0 -0
- package/phanpy/dist/assets/year-in-posts-DJv_star.js +0 -3
- package/phanpy/dist/assets/year-in-posts-DJv_star.js.map +0 -1
- package/phanpy/dist/compose/index.html +0 -20
- package/phanpy/dist/favicon.ico +0 -0
- package/phanpy/dist/index.html +0 -73
- package/phanpy/dist/logo-192.png +0 -0
- package/phanpy/dist/logo-512.png +0 -0
- package/phanpy/dist/logo-badge-72.png +0 -0
- package/phanpy/dist/logo-maskable-512.png +0 -0
- package/phanpy/dist/logo-monochrome-512.png +0 -0
- package/phanpy/dist/logo-monochrome-maskable-512.png +0 -0
- package/phanpy/dist/manifest.webmanifest +0 -1
- package/phanpy/dist/og-image-2.jpg +0 -0
- package/phanpy/dist/og-image.png +0 -0
- package/phanpy/dist/robots.txt +0 -2
- package/phanpy/dist/sw.js +0 -2
- package/phanpy/dist/sw.js.map +0 -1
- package/phanpy/dist/version.json +0 -1
- package/run-agent.mjs +0 -897
- package/vendor/gate.cjs +0 -138
- package/vendor/idp-grant.cjs +0 -394
- package/vendor/jfetch.cjs +0 -16
- package/web/admin/actors.js +0 -145
- package/web/admin/bar.css +0 -126
- package/web/admin/bar.js +0 -99
- package/web/admin/client/client.js +0 -66
- package/web/admin/client/index.html +0 -69
- package/web/admin/client-pick.js +0 -79
- package/web/admin/common.js +0 -23
- package/web/admin/connections.js +0 -112
- package/web/admin/gateway.js +0 -133
- package/web/admin/group.js +0 -258
- package/web/admin/index.html +0 -603
- package/web/admin/notices-bar.js +0 -90
- package/web/admin/oauth-signin.mjs +0 -37
- package/web/admin/record.js +0 -390
- package/web/admin/setup/index.html +0 -241
- package/web/admin/setup/setup.js +0 -326
- package/web/admin/tokens.css +0 -68
- package/web/admin/upkeep.js +0 -177
- package/web/admin/window.css +0 -55
- package/web/admin/window.js +0 -136
- package/web/app/README.md +0 -81
- package/web/app/admin-facade.mjs +0 -588
- package/web/app/agent.mjs +0 -663
- package/web/app/atproto-browser.mjs +0 -86
- package/web/app/authorize_interaction.html +0 -56
- package/web/app/authorize_interaction.js +0 -69
- package/web/app/boot.mjs +0 -576
- package/web/app/deliver-relay.mjs +0 -129
- package/web/app/dist/boot.js +0 -34218
- package/web/app/dist/boot.js.map +0 -7
- package/web/app/dist/sw.js +0 -72810
- package/web/app/dist/sw.js.map +0 -7
- package/web/app/fediacct-browser.mjs +0 -146
- package/web/app/gateway-move.mjs +0 -60
- package/web/app/idb-kv.mjs +0 -35
- package/web/app/index.html +0 -242
- package/web/app/keys-browser.mjs +0 -71
- package/web/app/keystore.mjs +0 -82
- package/web/app/oidc-session.mjs +0 -6
- package/web/app/pod-auth.mjs +0 -248
- package/web/app/pod-remote.mjs +0 -84
- package/web/app/shims/_globals.mjs +0 -2
- package/web/app/shims/fedify-sig.mjs +0 -77
- package/web/app/shims/node-crypto.mjs +0 -113
- package/web/app/shims/node-fs.mjs +0 -17
- package/web/app/shims/node-path.mjs +0 -19
- package/web/app/shims/node-url.mjs +0 -7
- package/web/app/shims/prelude.js +0 -58
- package/web/app/shims/safefetch.mjs +0 -58
- package/web/app/shims/shapes-text.mjs +0 -8
- package/web/app/shims/web-push.mjs +0 -14
- package/web/app/signup.mjs +0 -318
- package/web/app/sw-src.mjs +0 -220
- package/web/app/update.js +0 -70
- package/web/front/admin.html +0 -55
- package/web/front/admin.js +0 -89
- package/web/front/install.sh +0 -69
- package/web/front/new-account.html +0 -35
- package/web/front/new-account.js +0 -13
- package/web/front/notices.html +0 -69
- package/web/front/notices.js +0 -107
- package/web/front/run.html +0 -58
- package/web/front/run.js +0 -195
- package/web/front/solid-oidc-client.js +0 -6
- package/web/icons/forum.svg +0 -11
- package/web/icons/gateway.svg +0 -14
- 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
|
-
}
|
package/lib/session/package.json
DELETED
|
@@ -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
|
-
}
|
package/lib/shared/guard.mjs
DELETED
|
@@ -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
|
-
}
|
package/lib/shared/links.mjs
DELETED
|
@@ -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';
|