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,146 +0,0 @@
|
|
|
1
|
-
// fediacct-browser.mjs — connections to fediverse accounts the owner holds on
|
|
2
|
-
// OTHER servers, in the browser.
|
|
3
|
-
//
|
|
4
|
-
// Same FediAccounts client the Node agent uses (lib/fediacct.mjs): the same
|
|
5
|
-
// OAuth dance and authenticated calls. What the browser adds is the owner's
|
|
6
|
-
// per-connection storage choice, the same one Bluesky has:
|
|
7
|
-
//
|
|
8
|
-
// - "browser": the credential lives in IndexedDB on this device (default).
|
|
9
|
-
// - "pod": it lives in the owner-only pod state, so it follows the owner to
|
|
10
|
-
// any browser they sign in from.
|
|
11
|
-
//
|
|
12
|
-
// A record carries which backend it lives in; list() merges both, and moving a
|
|
13
|
-
// connection is a re-write to the other backend. App registrations and the
|
|
14
|
-
// short-lived OAuth "pending" state stay in IndexedDB (per browser), and the
|
|
15
|
-
// pending state is persisted so a connection survives the worker being
|
|
16
|
-
// idle-killed between the redirect out and the code coming back.
|
|
17
|
-
import { FediAccounts } from '../../lib/connections/fediacct.mjs';
|
|
18
|
-
import { kvAll, kvPut, kvDel } from './idb-kv.mjs';
|
|
19
|
-
|
|
20
|
-
const POD_DOC = 'fediaccts.json'; // { id: record } for pod-stored connections
|
|
21
|
-
const PEND_TTL_MS = 10 * 60_000;
|
|
22
|
-
|
|
23
|
-
export class BrowserFediAccounts extends FediAccounts {
|
|
24
|
-
constructor({ store, actorId = null, log = console.log } = {}) {
|
|
25
|
-
super({ localDir: '/fediaccts', actorId, log }); // localDir unused; storage below
|
|
26
|
-
this.store = store;
|
|
27
|
-
this._recs = new Map(); // browser (IndexedDB) records, id → record
|
|
28
|
-
this._apps = new Map(); // host → app registration
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async load() {
|
|
32
|
-
const all = await kvAll().catch(() => ({}));
|
|
33
|
-
for (const [k, v] of Object.entries(all)) {
|
|
34
|
-
if (k.startsWith('rec:')) this._recs.set(k.slice(4), v);
|
|
35
|
-
else if (k.startsWith('app:')) this._apps.set(k.slice(4), v);
|
|
36
|
-
else if (k.startsWith('pend:')) this.pending.set(k.slice(5), v);
|
|
37
|
-
}
|
|
38
|
-
const now = Date.now();
|
|
39
|
-
for (const [s, p] of this.pending) if (now - p.at > PEND_TTL_MS) { this.pending.delete(s); kvDel('pend:' + s).catch(() => {}); }
|
|
40
|
-
return this;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
_podMap() { return this.store.read(POD_DOC, {}) || {}; }
|
|
44
|
-
|
|
45
|
-
// ---- records, across both backends ----
|
|
46
|
-
ids() { return [...new Set([...this._recs.keys(), ...Object.keys(this._podMap())])]; }
|
|
47
|
-
|
|
48
|
-
read(id) {
|
|
49
|
-
let rec = null; let where = null;
|
|
50
|
-
if (this._recs.has(id)) { rec = this._recs.get(id); where = 'browser'; }
|
|
51
|
-
else { const p = this._podMap()[id]; if (p) { rec = p; where = 'pod'; } }
|
|
52
|
-
if (!rec) return null;
|
|
53
|
-
if (rec.mintedFor && this.actorId && rec.mintedFor !== this.actorId) {
|
|
54
|
-
this.log(`fediaccts/${id} belongs to ${rec.mintedFor} — not reusing it for ${this.actorId}`);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
return { ...rec, storage: where };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
write(rec) {
|
|
61
|
-
const where = rec.storage || 'browser';
|
|
62
|
-
const clean = { ...rec, storage: where };
|
|
63
|
-
if (where === 'pod') {
|
|
64
|
-
const map = { ...this._podMap(), [clean.id]: clean };
|
|
65
|
-
this.store.write(POD_DOC, map); this.store.flush?.().catch(() => {});
|
|
66
|
-
this._recs.delete(clean.id); kvDel('rec:' + clean.id).catch(() => {});
|
|
67
|
-
} else {
|
|
68
|
-
this._recs.set(clean.id, clean); kvPut('rec:' + clean.id, clean).catch((e) => this.log(`fediacct persist: ${e.message}`));
|
|
69
|
-
const map = this._podMap();
|
|
70
|
-
if (clean.id in map) { delete map[clean.id]; this.store.write(POD_DOC, map); this.store.flush?.().catch(() => {}); }
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Best-effort: tell the server to revoke our token (RFC 7009), so a
|
|
75
|
-
// disconnect actually invalidates it there — not just locally. Mirrors
|
|
76
|
-
// atproto's deleteSession. Called before remove(); a failure never blocks the
|
|
77
|
-
// local removal. For a pod-stored connection this is what lets one device
|
|
78
|
-
// revoke a token another device (a stolen phone) is holding.
|
|
79
|
-
async revoke(id) {
|
|
80
|
-
const rec = this.read(id);
|
|
81
|
-
const app = this._apps.get(rec?.host);
|
|
82
|
-
if (!rec?.token || !app?.clientId) return;
|
|
83
|
-
try {
|
|
84
|
-
await this._fetch(rec.host, `https://${rec.host}/oauth/revoke`, {
|
|
85
|
-
method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
86
|
-
body: new URLSearchParams({ client_id: app.clientId, client_secret: app.clientSecret, token: rec.token }).toString(),
|
|
87
|
-
});
|
|
88
|
-
this.log(`revoked our token at ${rec.host}`);
|
|
89
|
-
} catch (e) { this.log(`revoke at ${rec.host} failed (removing locally anyway): ${e.message}`); }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
remove(id) {
|
|
93
|
-
const had = this._recs.has(id) || (id in this._podMap());
|
|
94
|
-
if (!had) return false;
|
|
95
|
-
this._recs.delete(id); kvDel('rec:' + id).catch(() => {});
|
|
96
|
-
const map = this._podMap();
|
|
97
|
-
if (id in map) { delete map[id]; this.store.write(POD_DOC, map); this.store.flush?.().catch(() => {}); }
|
|
98
|
-
this.log(`Fediverse account disconnected: ${id}`);
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Carry the storage location out to the record page.
|
|
103
|
-
roster() { return this.list().map(r => ({ id: r.id, handle: r.handle, host: r.host, addedAt: r.addedAt || null, enabled: r.enabled !== false, storage: r.storage })); }
|
|
104
|
-
status() {
|
|
105
|
-
return this.list().map(r => ({
|
|
106
|
-
id: r.id, handle: r.handle, host: r.host, enabled: r.enabled !== false,
|
|
107
|
-
needsReconnect: !!r.needsReconnect, storage: r.storage,
|
|
108
|
-
cooldownFor: Math.max(0, Math.round(((this.pausedUntil.get(r.host) || 0) - Date.now()) / 1000)),
|
|
109
|
-
}));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Move a connection between backends (the Options menu's Storage control).
|
|
113
|
-
setStorage(id, where) { const rec = this.read(id); if (rec && (where === 'pod' || where === 'browser')) { this.write({ ...rec, storage: where }); return this.roster().find(r => r.id === id) || null; } return null; }
|
|
114
|
-
|
|
115
|
-
// One app registration per host, remembered — over IndexedDB.
|
|
116
|
-
async appFor(host, redirectUri) {
|
|
117
|
-
const cached = this._apps.get(host);
|
|
118
|
-
if (cached && cached.redirectUri === redirectUri && cached.clientId && cached.clientSecret) return cached;
|
|
119
|
-
const res = await this._fetch(host, `https://${host}/api/v1/apps`, {
|
|
120
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
121
|
-
body: JSON.stringify({ client_name: 'FediPod', redirect_uris: redirectUri, scopes: 'read write', website: 'https://github.com/jeff-zucker/FediPod' }),
|
|
122
|
-
});
|
|
123
|
-
const body = await this._json(res, host);
|
|
124
|
-
if (!body.client_id || !body.client_secret) throw new Error(`${host} registered no client`);
|
|
125
|
-
const app = { host, redirectUri, clientId: body.client_id, clientSecret: body.client_secret };
|
|
126
|
-
this._apps.set(host, app); await kvPut('app:' + host, app).catch(() => {});
|
|
127
|
-
return app;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Persist the pending state across the redirect (the worker may be idle-killed).
|
|
131
|
-
async begin(opts) {
|
|
132
|
-
const r = await super.begin(opts);
|
|
133
|
-
await kvPut('pend:' + r.state, this.pending.get(r.state)).catch(() => {});
|
|
134
|
-
return r;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async complete({ state, code }) {
|
|
138
|
-
if (!this.pending.has(state)) {
|
|
139
|
-
const p = await kvAll().then((a) => a['pend:' + state]).catch(() => null);
|
|
140
|
-
if (p) this.pending.set(state, p);
|
|
141
|
-
}
|
|
142
|
-
const row = await super.complete({ state, code });
|
|
143
|
-
await kvDel('pend:' + state).catch(() => {});
|
|
144
|
-
return row;
|
|
145
|
-
}
|
|
146
|
-
}
|
package/web/app/gateway-move.mjs
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
// gateway-move.mjs — the second half of moving an address from one gateway
|
|
2
|
-
// to another (signup.mjs moveIn is the first). It runs on the agent's first
|
|
3
|
-
// active boot under the new ids, once the new actor is published with the
|
|
4
|
-
// old one as an alias, and it is safe to run again: nothing here is done
|
|
5
|
-
// twice with the same result changed.
|
|
6
|
-
//
|
|
7
|
-
// 1. The old gateway is told the address moved (POST /api/move, proved
|
|
8
|
-
// with the pod session as attach was). It serves the old actor as a
|
|
9
|
-
// moved stub from then on — same key, old id, `movedTo` the new one.
|
|
10
|
-
// 2. A Move goes to every follower, FROM the old actor, signed under the
|
|
11
|
-
// old key id, through the old gateway's relay: that is the only
|
|
12
|
-
// signature a follower's server can verify against the old actor.
|
|
13
|
-
//
|
|
14
|
-
// The followers' servers fetch the old actor, find `movedTo`, fetch the new
|
|
15
|
-
// one, find the old among its `alsoKnownAs`, and move the follow.
|
|
16
|
-
import { RelayDeliverer } from './deliver-relay.mjs';
|
|
17
|
-
import { moveActivity } from '../../lib/core/wire.mjs';
|
|
18
|
-
|
|
19
|
-
export async function completeGatewayMove(agent) {
|
|
20
|
-
const cfg = agent.store.getConfig();
|
|
21
|
-
const mv = cfg?.movedFrom;
|
|
22
|
-
if (!mv || mv.completedAt) return null;
|
|
23
|
-
const log = agent.log;
|
|
24
|
-
const newActor = agent.urls.actor;
|
|
25
|
-
|
|
26
|
-
// 1. the old gateway
|
|
27
|
-
let told;
|
|
28
|
-
try {
|
|
29
|
-
told = await agent.sessionFetch(`${mv.gateway}/api/move`, {
|
|
30
|
-
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
31
|
-
body: JSON.stringify({ handle: mv.handle, movedTo: newActor }),
|
|
32
|
-
});
|
|
33
|
-
} catch (e) { told = { status: 0, statusText: e.message }; }
|
|
34
|
-
if (told.status !== 200) {
|
|
35
|
-
// Left pending: the next active boot tries again. Followers are not told
|
|
36
|
-
// until the old actor can vouch for the Move.
|
|
37
|
-
log(`gateway move: ${mv.gateway} did not mark @${mv.handle} as moved (${told.status || told.statusText}) — will retry`);
|
|
38
|
-
return { told: false };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// 2. the Move, from the old address
|
|
42
|
-
const contacts = agent.store.getContacts();
|
|
43
|
-
const inboxes = [...new Set(contacts.followers.map((f) => f.sharedInbox || f.inbox).filter(Boolean))];
|
|
44
|
-
const activity = moveActivity({ actor: mv.actor }, newActor, Date.now());
|
|
45
|
-
const sender = new RelayDeliverer({
|
|
46
|
-
passive: true, store: agent.store, rsaPrivate: agent.deliverer.rsaPrivate,
|
|
47
|
-
keyId: `${mv.actor}#main-key`, actorId: mv.actor, log,
|
|
48
|
-
relayUrl: `${mv.gateway}/api/relay`, handle: mv.handle, sessionFetch: agent.sessionFetch,
|
|
49
|
-
});
|
|
50
|
-
let sent = 0; let failed = 0;
|
|
51
|
-
for (const inbox of inboxes) {
|
|
52
|
-
try { await sender.deliverNow(inbox, activity); sent++; }
|
|
53
|
-
catch (e) { failed++; log(`gateway move: Move to ${inbox} failed: ${e.message}`); }
|
|
54
|
-
}
|
|
55
|
-
const done = { ...mv, completedAt: new Date().toISOString(), moveSent: sent, moveFailed: failed };
|
|
56
|
-
agent.store.setConfig({ ...agent.store.getConfig(), movedFrom: done });
|
|
57
|
-
await agent.store.flush?.();
|
|
58
|
-
log(`moved from ${mv.actor} to ${newActor}: Move sent to ${sent} inbox(es)${failed ? `, ${failed} failed` : ''}`);
|
|
59
|
-
return done;
|
|
60
|
-
}
|
package/web/app/idb-kv.mjs
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
// idb-kv.mjs — a tiny per-origin key/value store on IndexedDB, for the browser
|
|
2
|
-
// agent's "on this device, never the pod" storage: a connected account's
|
|
3
|
-
// credential when the owner chooses to keep it in this browser rather than let
|
|
4
|
-
// it follow them to their pod. Shared by atproto-browser and fediacct-browser.
|
|
5
|
-
const DB = 'fedipod-accounts';
|
|
6
|
-
|
|
7
|
-
function open() {
|
|
8
|
-
return new Promise((res, rej) => {
|
|
9
|
-
const r = indexedDB.open(DB, 1);
|
|
10
|
-
r.onupgradeneeded = () => { const db = r.result; if (!db.objectStoreNames.contains('kv')) db.createObjectStore('kv'); };
|
|
11
|
-
r.onsuccess = () => res(r.result);
|
|
12
|
-
r.onerror = () => rej(r.error);
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function kvGet(key) {
|
|
17
|
-
const db = await open();
|
|
18
|
-
return new Promise((res, rej) => { const rq = db.transaction('kv', 'readonly').objectStore('kv').get(key); rq.onsuccess = () => res(rq.result ?? null); rq.onerror = () => rej(rq.error); });
|
|
19
|
-
}
|
|
20
|
-
export async function kvAll() {
|
|
21
|
-
const db = await open();
|
|
22
|
-
return new Promise((res, rej) => {
|
|
23
|
-
const out = {}; const cur = db.transaction('kv', 'readonly').objectStore('kv').openCursor();
|
|
24
|
-
cur.onsuccess = () => { const c = cur.result; if (c) { out[c.key] = c.value; c.continue(); } else res(out); };
|
|
25
|
-
cur.onerror = () => rej(cur.error);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
export async function kvPut(key, val) {
|
|
29
|
-
const db = await open();
|
|
30
|
-
return new Promise((res, rej) => { const tx = db.transaction('kv', 'readwrite'); tx.objectStore('kv').put(val, key); tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); });
|
|
31
|
-
}
|
|
32
|
-
export async function kvDel(key) {
|
|
33
|
-
const db = await open();
|
|
34
|
-
return new Promise((res, rej) => { const tx = db.transaction('kv', 'readwrite'); tx.objectStore('kv').delete(key); tx.oncomplete = () => res(); tx.onerror = () => rej(tx.error); });
|
|
35
|
-
}
|
package/web/app/index.html
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' https:; form-action 'self'; base-uri 'none'">
|
|
7
|
-
<title>FediPod</title>
|
|
8
|
-
<link rel="icon" href="/icons/local.svg" type="image/svg+xml">
|
|
9
|
-
<style>
|
|
10
|
-
:root {
|
|
11
|
-
color-scheme: light dark;
|
|
12
|
-
font-size: 125%; /* 20px default (125% of 16) */
|
|
13
|
-
--bg: #ffffff; --fg: #1a1a1a;
|
|
14
|
-
--heading: #2c4a35; --sub: #565656; --line: #b0b0b0; --accent: #12467e;
|
|
15
|
-
--btn: #3a5f43; --btn-hover: #30503a; --btn-text: #ffffff;
|
|
16
|
-
--field-bg: #ececec; --field-text: #141414; --field-border: #6f6f6f;
|
|
17
|
-
--select-bg: #dce7f3; --select-border: #12467e;
|
|
18
|
-
--err: #b00020; --warn: #8a4b00; --focus: #12467e;
|
|
19
|
-
}
|
|
20
|
-
@media (prefers-color-scheme: dark) {
|
|
21
|
-
:root {
|
|
22
|
-
--bg: #191919; --fg: #f3f3f3;
|
|
23
|
-
--heading: #a7d4b5; --sub: #d2d2d2; --line: #6d6d6d; --accent: #8fc2f2;
|
|
24
|
-
--field-bg: #474747; --field-text: #f5f5f5; --field-border: #9aa0a6;
|
|
25
|
-
--select-bg: #2f4a66; --select-border: #8fc2f2;
|
|
26
|
-
--btn: #4a7a54; --btn-hover: #3f6a49;
|
|
27
|
-
--err: #ff9b9b; --warn: #ffb366; --focus: #8fc2f2;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
body { font: 1rem/1.55 Arial, Helvetica, sans-serif; margin: 0; background: var(--bg); color: var(--fg); }
|
|
31
|
-
#page { max-width: 42rem; margin: 0 auto; padding: 1.75rem 1rem 4rem; box-sizing: border-box; }
|
|
32
|
-
h1, h2, h3 { color: var(--heading); }
|
|
33
|
-
h1 { font-size: 1.6rem; margin: 0 0 .5rem; }
|
|
34
|
-
#brand { margin: 0 0 1.25rem; }
|
|
35
|
-
h2 { font-size: 1.2rem; margin: 2rem 0 .5rem; }
|
|
36
|
-
p { margin: .6rem 0; }
|
|
37
|
-
code { background: rgba(120,120,120,.18); padding: .05em .35em; border-radius: 4px; }
|
|
38
|
-
.sub { color: var(--sub); margin-top: 0; }
|
|
39
|
-
.hint { color: var(--sub); font-size: .95rem; margin: .15rem 0 0; } /* ~17px, never under 16 */
|
|
40
|
-
.grouplabel { display: block; margin: .75rem 0 .2rem; font-weight: 600; }
|
|
41
|
-
fieldset { border: 1px solid var(--line); border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
|
|
42
|
-
legend { padding: 0 .3rem; font-weight: 600; }
|
|
43
|
-
label { display: block; margin: .75rem 0 .2rem; }
|
|
44
|
-
input[type=text], input[type=email], input[type=url], input[type=password], select {
|
|
45
|
-
font: inherit; font-size: 1rem; width: 100%; padding: .55rem; box-sizing: border-box;
|
|
46
|
-
border: 1px solid var(--field-border); border-radius: .3rem;
|
|
47
|
-
background: var(--field-bg); color: var(--field-text); }
|
|
48
|
-
/* A list to pick from looks different from a box to type in: its own tint,
|
|
49
|
-
the accent border, and the same on its options. */
|
|
50
|
-
select { background: var(--select-bg); border-color: var(--select-border); }
|
|
51
|
-
select option { background: var(--select-bg); color: var(--field-text); }
|
|
52
|
-
::placeholder { color: var(--sub); opacity: 1; }
|
|
53
|
-
.choice { display: block; margin: .4rem 0; font-weight: normal; }
|
|
54
|
-
.choice input { margin-right: .5rem; width: auto; }
|
|
55
|
-
button { font: inherit; font-size: 1rem; padding: .6rem 1.3rem; border-radius: .4rem;
|
|
56
|
-
border: 1px solid var(--btn); background: var(--btn); color: var(--btn-text);
|
|
57
|
-
font-weight: 600; cursor: pointer; }
|
|
58
|
-
button:hover { background: var(--btn-hover); border-color: var(--btn-hover); }
|
|
59
|
-
button.primary { font-weight: 700; }
|
|
60
|
-
button:disabled { opacity: .55; cursor: default; }
|
|
61
|
-
#create { margin-bottom: 2rem; }
|
|
62
|
-
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
|
|
63
|
-
.row { display: flex; gap: .5rem; align-items: center; }
|
|
64
|
-
.row input { flex: 1; }
|
|
65
|
-
.row button { white-space: nowrap; }
|
|
66
|
-
.address { font-size: 1.25rem; font-weight: 700; word-break: break-all; color: var(--fg); }
|
|
67
|
-
.warn { border-left: .25rem solid var(--warn); padding-left: .75rem; color: var(--warn); }
|
|
68
|
-
.err { border-left: .25rem solid var(--err); padding-left: .75rem; color: var(--err); }
|
|
69
|
-
.err:empty { display: none; }
|
|
70
|
-
.steps { list-style: none; padding: 0; }
|
|
71
|
-
.steps li { padding: .3rem 0; }
|
|
72
|
-
a { color: var(--accent); }
|
|
73
|
-
[hidden] { display: none !important; }
|
|
74
|
-
</style>
|
|
75
|
-
</head>
|
|
76
|
-
<body>
|
|
77
|
-
<main id="page">
|
|
78
|
-
|
|
79
|
-
<!-- The marketing hero: shown on the landing, hidden once a form is up. -->
|
|
80
|
-
<header id="hero">
|
|
81
|
-
<h1>FediPod - join the Fediverse from a Solid pod.</h1>
|
|
82
|
-
<p>Welcome to <b>fedipod.net</b>! By creating an account here, you can get a free Solid pod
|
|
83
|
-
and Fediverse identity or link to your existing pod/identity. Your fedipod.net
|
|
84
|
-
account provides you a portal to the Fediverse and filters unverified mail.</p>
|
|
85
|
-
|
|
86
|
-
<p>There is nothing to install, once you've setup an account, just point any browser at
|
|
87
|
-
<code>https://fedipod.net</code> and sign in.</p>
|
|
88
|
-
</header>
|
|
89
|
-
|
|
90
|
-
<!-- The short brand: shown only when a form is up, so the form stands alone. -->
|
|
91
|
-
<h1 id="brand" hidden>FediPod</h1>
|
|
92
|
-
|
|
93
|
-
<p id="loading" role="status">Loading…</p>
|
|
94
|
-
|
|
95
|
-
<section id="landing" hidden>
|
|
96
|
-
<h2>Already have a FediPod account?</h2>
|
|
97
|
-
<p class="row">
|
|
98
|
-
<input type="text" id="signin-address" autocomplete="username" autocapitalize="off" spellcheck="false" placeholder="@me@mypod"
|
|
99
|
-
aria-label="Your FediPod address, in the form @you@yourpod">
|
|
100
|
-
<button type="button" class="primary" id="signin">sign in</button>
|
|
101
|
-
</p>
|
|
102
|
-
<p class="err" id="signin-error" role="alert"></p>
|
|
103
|
-
<h2>New user?</h2>
|
|
104
|
-
<p><button type="button" class="primary" id="create">create an account</button></p>
|
|
105
|
-
<p>Fedipod.net is a FediPod Gateway operated by Jeff Zucker. You can detach from the gateway at any
|
|
106
|
-
time and transfer your account either to your own FediPod Gateway (easy to set up on a free tier Netlify)
|
|
107
|
-
or to any Fediverse server. All of your data lives on your pod and is yours regardless of which
|
|
108
|
-
gateway you are attached to.</p>
|
|
109
|
-
<p>For more information, see
|
|
110
|
-
<a href="https://github.com/jeff-zucker/FediPod">github.com/jeff-zucker/FediPod</a></p>
|
|
111
|
-
</section>
|
|
112
|
-
|
|
113
|
-
<!-- Shown only for an account made before 2026-09-22, whose signing key on
|
|
114
|
-
the pod is still under the password given at sign-up. It is opened once
|
|
115
|
-
and stored as it is from then on. The second block is for someone who no
|
|
116
|
-
longer has that password: a new key. -->
|
|
117
|
-
<section id="unlock" hidden>
|
|
118
|
-
<h2>Open your signing key</h2>
|
|
119
|
-
<p>Your account was made when this site kept your signing key under the
|
|
120
|
-
password you gave at sign-up. Enter that password once. Your key is then
|
|
121
|
-
stored on your pod as it is, behind your pod's own login, and no browser
|
|
122
|
-
asks again.</p>
|
|
123
|
-
<p class="row">
|
|
124
|
-
<input type="password" id="unlock-password" autocomplete="current-password"
|
|
125
|
-
placeholder="The password you gave at sign-up" aria-label="The password you gave at sign-up">
|
|
126
|
-
<button type="button" class="primary" id="unlock-go">open</button>
|
|
127
|
-
</p>
|
|
128
|
-
<p class="err" id="unlock-error" role="alert"></p>
|
|
129
|
-
<p class="hint">This browser only — it is not sent anywhere.</p>
|
|
130
|
-
<h2>Lost that password?</h2>
|
|
131
|
-
<p>Make a new signing key instead. Your account, address, posts and followers
|
|
132
|
-
stay as they are; other servers learn the new key the next time they hear
|
|
133
|
-
from you.</p>
|
|
134
|
-
<p><button type="button" id="unlock-newkey">make a new signing key</button></p>
|
|
135
|
-
<p id="unlock-newkey-confirm" hidden>This replaces the key on your pod.
|
|
136
|
-
<button type="button" id="unlock-newkey-go">make the new key</button></p>
|
|
137
|
-
</section>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
<section id="pane-form" hidden>
|
|
141
|
-
<form id="form">
|
|
142
|
-
|
|
143
|
-
<!-- Screen 1: Your Solid Pod -->
|
|
144
|
-
<div class="step" id="step-1">
|
|
145
|
-
<fieldset>
|
|
146
|
-
<legend>Your Solid Pod</legend>
|
|
147
|
-
|
|
148
|
-
<div role="radiogroup" aria-labelledby="podtype-label">
|
|
149
|
-
<span id="podtype-label" class="grouplabel">Pod type</span>
|
|
150
|
-
<label class="choice"><input type="radio" name="mode" value="existing"> An existing pod</label>
|
|
151
|
-
<label class="choice"><input type="radio" name="mode" value="new" checked> A new pod</label>
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
<label for="provider">Pod provider</label>
|
|
155
|
-
<!-- The providers listed at https://solidproject.org/get_a_pod that run Community
|
|
156
|
-
Solid Server, whose sign-up page "Create your pod" links to. Checked 2026-09-14. -->
|
|
157
|
-
<select id="provider" name="provider">
|
|
158
|
-
<option value="https://solidcommunity.net">solidcommunity.net</option>
|
|
159
|
-
<option value="https://privatedatapod.com">privatedatapod.com</option>
|
|
160
|
-
<option value="https://solid.redpencil.io">redpencil.io</option>
|
|
161
|
-
<option value="https://pods.solidcommunity.au">solidcommunity.au</option>
|
|
162
|
-
<option value="https://solidweb.me">solidweb.me</option>
|
|
163
|
-
<option value="https://teamid.live">teamid.live</option>
|
|
164
|
-
<option value="">Other…</option>
|
|
165
|
-
</select>
|
|
166
|
-
<div id="provider-other-field" hidden>
|
|
167
|
-
<label for="providerOther">Provider address</label>
|
|
168
|
-
<input type="url" id="providerOther" name="providerOther" autocomplete="url" placeholder="https://pods.example.org">
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
<div id="newpod-field">
|
|
172
|
-
<p>Make your pod on your provider's own page, then come back here.</p>
|
|
173
|
-
<p><a id="register-link" href="#" target="_blank" rel="noopener">Create your pod at your provider</a></p>
|
|
174
|
-
</div>
|
|
175
|
-
|
|
176
|
-
<p class="hint" id="signin-hint">Your password is typed at your pod only. This site
|
|
177
|
-
never sees it: you sign in at your pod, and it sends you back here.</p>
|
|
178
|
-
</fieldset>
|
|
179
|
-
<p class="err" id="err-1" role="alert"></p>
|
|
180
|
-
<p>
|
|
181
|
-
<button type="button" id="cancel">Cancel</button>
|
|
182
|
-
<button type="button" class="primary" id="to-2">Sign in at your pod</button>
|
|
183
|
-
</p>
|
|
184
|
-
</div>
|
|
185
|
-
|
|
186
|
-
<!-- Screen 2: Your Fediverse Identity — shown on return from the pod's login -->
|
|
187
|
-
<div class="step" id="step-2" hidden>
|
|
188
|
-
<fieldset>
|
|
189
|
-
<legend>Your Fediverse Identity</legend>
|
|
190
|
-
|
|
191
|
-
<span id="signed-pod-label" class="grouplabel">Your pod</span>
|
|
192
|
-
<p id="signed-pod" role="status" aria-labelledby="signed-pod-label"></p>
|
|
193
|
-
|
|
194
|
-
<!-- Shown when the pod's account lives at another gateway: this is a
|
|
195
|
-
move, not a new account. -->
|
|
196
|
-
<p id="movein-note" hidden>Moving <strong id="movein-from"></strong> to this site.
|
|
197
|
-
Your posts, followers and key come with you, and your followers are told the
|
|
198
|
-
new address. Keep the same handle, or choose a new one here.</p>
|
|
199
|
-
|
|
200
|
-
<label for="handle">Fediverse handle</label>
|
|
201
|
-
<input type="text" id="handle" name="handle" autocomplete="off"
|
|
202
|
-
placeholder="the handle part of your address: @HANDLE@server" aria-describedby="handle-hint">
|
|
203
|
-
<p class="hint" id="handle-hint">The name in your Fediverse address. This is permanent.
|
|
204
|
-
Letters, digits, and hyphens only.</p>
|
|
205
|
-
|
|
206
|
-
<div role="radiogroup" aria-labelledby="shape-label" id="shape-group">
|
|
207
|
-
<span id="shape-label" class="grouplabel">Where your address lives</span>
|
|
208
|
-
<label class="choice"><input type="radio" name="shape" value="pod" checked> On your pod</label>
|
|
209
|
-
<label class="choice"><input type="radio" name="shape" value="front"> At this site</label>
|
|
210
|
-
</div>
|
|
211
|
-
<p class="hint" id="shape-hint" hidden>Your pod provider uses suffixes, so your only option
|
|
212
|
-
is to use an account on a gateway like this one.</p>
|
|
213
|
-
|
|
214
|
-
<span id="addr-label" class="grouplabel">Your Fediverse address</span>
|
|
215
|
-
<p class="address" id="preview" role="status" aria-live="polite" aria-labelledby="addr-label">@…@…</p>
|
|
216
|
-
</fieldset>
|
|
217
|
-
<p class="hint">The handle and the pod cannot be changed afterwards. Everything else — display
|
|
218
|
-
name, bio, pictures — you set later in the client.</p>
|
|
219
|
-
<p class="err" id="form-error" role="alert"></p>
|
|
220
|
-
<p>
|
|
221
|
-
<button type="button" id="back-1">Back</button>
|
|
222
|
-
<button type="submit" class="primary" id="submit">Create your Fediverse account</button>
|
|
223
|
-
</p>
|
|
224
|
-
</div>
|
|
225
|
-
|
|
226
|
-
</form>
|
|
227
|
-
</section>
|
|
228
|
-
|
|
229
|
-
<section id="running" hidden>
|
|
230
|
-
<h2 id="running-title">Setting up…</h2>
|
|
231
|
-
<ul class="steps" id="steps" aria-live="polite"></ul>
|
|
232
|
-
<p class="err" id="run-error" role="alert"></p>
|
|
233
|
-
<p id="run-actions" hidden>
|
|
234
|
-
<button type="button" id="run-back">Start over</button>
|
|
235
|
-
<button type="button" class="primary" id="run-retry">Try again</button>
|
|
236
|
-
</p>
|
|
237
|
-
</section>
|
|
238
|
-
|
|
239
|
-
</main>
|
|
240
|
-
<script type="module" src="/boot.js"></script>
|
|
241
|
-
</body>
|
|
242
|
-
</html>
|
package/web/app/keys-browser.mjs
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
// keys-browser.mjs — turn the keys record (from keystore/the pod) into what the
|
|
2
|
-
// agent's deliverer and publisher expect. The Node agent's resolveKeys returns
|
|
3
|
-
// { rsaPrivate (a WebCrypto key), rsaPublicPem, edPrivate, edPublicMultibase };
|
|
4
|
-
// this returns the same, RSA only. FEP-8b32 Ed25519 proofs are optional — the
|
|
5
|
-
// deliverer treats a null edPrivate as "no proof", and an unproved activity
|
|
6
|
-
// still federates — so the browser MVP omits them.
|
|
7
|
-
//
|
|
8
|
-
// The private key is imported NON-extractable, and the copy this browser keeps
|
|
9
|
-
// in IndexedDB is that CryptoKey, not the PEM: it can sign and cannot be read
|
|
10
|
-
// out, so a script that reaches this origin's storage gets nothing it can carry
|
|
11
|
-
// away. The PEM record exists only in memory, between unwrap and import.
|
|
12
|
-
import { kvGet, kvPut } from './idb-kv.mjs';
|
|
13
|
-
import * as podState from '../../lib/pod/state.mjs';
|
|
14
|
-
import { isKeyEnvelope, KeyPasswordNeeded } from './keystore.mjs';
|
|
15
|
-
|
|
16
|
-
const pemToDer = (pem) => Uint8Array.from(
|
|
17
|
-
atob(pem.replace(/-----[^-]+-----/g, '').replace(/\s+/g, '')), (c) => c.charCodeAt(0));
|
|
18
|
-
|
|
19
|
-
export async function importSigningKey(keysRecord) {
|
|
20
|
-
const rsaPrivate = await crypto.subtle.importKey('pkcs8', pemToDer(keysRecord.rsa.privatePem),
|
|
21
|
-
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']);
|
|
22
|
-
return { rsaPrivate, rsaPublicPem: keysRecord.rsa.publicPem, edPrivate: null, edPublicMultibase: null };
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** What the cache holds: the non-extractable key and the public half. */
|
|
26
|
-
const fromCache = (c) => ({ rsaPrivate: c.rsaPrivate, rsaPublicPem: c.rsaPublicPem, edPrivate: null, edPublicMultibase: null });
|
|
27
|
-
const isOpenedKey = (c) => c?.rsaPrivate?.type === 'private' && typeof c.rsaPublicPem === 'string';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Keep this browser's opened copy: import the record and store the CryptoKey.
|
|
31
|
-
* Best effort on the store — a browser that refuses IndexedDB (private mode)
|
|
32
|
-
* reads the pod's copy again on the way back in. Returns the imported keys.
|
|
33
|
-
*/
|
|
34
|
-
export async function cacheOpenedKeys(actorUrl, keysRecord) {
|
|
35
|
-
const keys = await importSigningKey(keysRecord);
|
|
36
|
-
await kvPut(keyCacheKey(actorUrl), { rsaPrivate: keys.rsaPrivate, rsaPublicPem: keys.rsaPublicPem })
|
|
37
|
-
.catch(() => {});
|
|
38
|
-
return keys;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Where this browser keeps its own opened copy of the signing key: a
|
|
42
|
-
// non-extractable CryptoKey that never leaves the origin — the same protection
|
|
43
|
-
// the OIDC session in the next IDB row already relies on. It is what lets the
|
|
44
|
-
// worker boot itself after an idle kill without a network read.
|
|
45
|
-
export const keyCacheKey = (actorUrl) => `signing-keys:${actorUrl}`;
|
|
46
|
-
// The cache is keyed by the POD actor, whatever the identity advertises: a
|
|
47
|
-
// fronted identity's advertised actor is the gateway's, and keying by that
|
|
48
|
-
// would hide the opened copy sign-up stored and read the pod again.
|
|
49
|
-
export const podActorOf = (urls) => (urls.toPod ? urls.toPod(urls.actor) : urls.actor);
|
|
50
|
-
|
|
51
|
-
// Read the signing key for this actor and import it for signing.
|
|
52
|
-
//
|
|
53
|
-
// Two places hold one: this browser (opened, in IndexedDB) and the pod. The
|
|
54
|
-
// browser's is tried first — no network read. Falling through to the pod covers
|
|
55
|
-
// the browser that has just signed in and has no copy yet: the record is read
|
|
56
|
-
// with the pod session and used as it stands. An envelope from before 1.28.0
|
|
57
|
-
// needs the password and so has to go back to the page.
|
|
58
|
-
export async function loadKeysFromPod(remote, urls) {
|
|
59
|
-
const podActor = podActorOf(urls);
|
|
60
|
-
const cached = await kvGet(keyCacheKey(podActor)).catch(() => null);
|
|
61
|
-
if (isOpenedKey(cached)) return fromCache(cached);
|
|
62
|
-
// A copy stored as PEM by an earlier build: import it and store the key form
|
|
63
|
-
// in its place, so the PEM is gone from storage after one boot.
|
|
64
|
-
if (cached?.rsa?.privatePem) return cacheOpenedKeys(podActor, cached);
|
|
65
|
-
|
|
66
|
-
const doc = await podState.readKeys(remote, urls);
|
|
67
|
-
if (isKeyEnvelope(doc)) throw new KeyPasswordNeeded();
|
|
68
|
-
if (!doc || !doc.rsa) throw new Error('no signing key on the pod — sign up did not finish');
|
|
69
|
-
// Cache it so the next boot is no read at all.
|
|
70
|
-
return cacheOpenedKeys(podActor, doc);
|
|
71
|
-
}
|
package/web/app/keystore.mjs
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
// keystore.mjs — the signing keys, made in the browser and stored on the pod.
|
|
2
|
-
//
|
|
3
|
-
// The Node agent keeps keys.json (RSA + Ed25519 PEMs) on disk, 0600. A browser
|
|
4
|
-
// has no disk it can carry to the next machine, so the durable copy lives on
|
|
5
|
-
// the pod, in an owner-only container: the same access rule every private
|
|
6
|
-
// document on the pod lives under, reachable through the pod's own login and
|
|
7
|
-
// by nobody else. The shape is exactly the agent's keys.json, so it is used
|
|
8
|
-
// unchanged. A new browser reads it with its pod session and asks for nothing.
|
|
9
|
-
//
|
|
10
|
-
// Until 2026-09-22 the pod's copy was encrypted under the pod password, so a
|
|
11
|
-
// new browser had to ask for the password once. That sealed the key against
|
|
12
|
-
// the pod's host, which the access rule cannot do, at the price of fedipod.net
|
|
13
|
-
// asking for a pod password. Jeff decided the host is trusted the way it is
|
|
14
|
-
// for every other document, and the password went. `unwrapKeys`,
|
|
15
|
-
// `isKeyEnvelope` and `KeyPasswordNeeded` remain for accounts made before
|
|
16
|
-
// then: their key is opened once with the password and stored as it is.
|
|
17
|
-
|
|
18
|
-
const PEM = (der, label) => {
|
|
19
|
-
const b64 = btoa(String.fromCharCode(...new Uint8Array(der)));
|
|
20
|
-
const lines = b64.match(/.{1,64}/g).join('\n');
|
|
21
|
-
return `-----BEGIN ${label}-----\n${lines}\n-----END ${label}-----\n`;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
/** RSA (what Mastodon verifies) and, where the browser supports it, Ed25519
|
|
25
|
-
* (FEP-8b32 proofs). A missing Ed25519 half is not fatal — the agent mints one
|
|
26
|
-
* the same way it does for a pre-proofs keys.json, since nothing has published it. */
|
|
27
|
-
export async function generateKeys() {
|
|
28
|
-
const rsa = await crypto.subtle.generateKey(
|
|
29
|
-
{ name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },
|
|
30
|
-
true, ['sign', 'verify']);
|
|
31
|
-
const rec = {
|
|
32
|
-
rsa: {
|
|
33
|
-
publicPem: PEM(await crypto.subtle.exportKey('spki', rsa.publicKey), 'PUBLIC KEY'),
|
|
34
|
-
privatePem: PEM(await crypto.subtle.exportKey('pkcs8', rsa.privateKey), 'PRIVATE KEY'),
|
|
35
|
-
},
|
|
36
|
-
};
|
|
37
|
-
try {
|
|
38
|
-
const ed = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']);
|
|
39
|
-
rec.ed25519 = {
|
|
40
|
-
publicPem: PEM(await crypto.subtle.exportKey('spki', ed.publicKey), 'PUBLIC KEY'),
|
|
41
|
-
privatePem: PEM(await crypto.subtle.exportKey('pkcs8', ed.privateKey), 'PRIVATE KEY'),
|
|
42
|
-
};
|
|
43
|
-
} catch { /* no Ed25519 in this browser — the agent mints it on first run */ }
|
|
44
|
-
return rec;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const PBKDF2_ITERATIONS = 310_000; // OWASP 2023 floor for PBKDF2-HMAC-SHA256
|
|
48
|
-
|
|
49
|
-
async function deriveAesKey(password, salt, iterations) {
|
|
50
|
-
const base = await crypto.subtle.importKey('raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey']);
|
|
51
|
-
return crypto.subtle.deriveKey(
|
|
52
|
-
{ name: 'PBKDF2', salt, iterations, hash: 'SHA-256' },
|
|
53
|
-
base, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const fromB64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
|
|
57
|
-
|
|
58
|
-
/** Whether a document read off the pod is an envelope from before 1.28.0
|
|
59
|
-
* rather than the keys record. The shape is what tells them apart. */
|
|
60
|
-
export function isKeyEnvelope(doc) {
|
|
61
|
-
return !!doc && doc.v === 1 && typeof doc.ct === 'string' && typeof doc.salt === 'string';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Thrown when the pod holds a key from before 1.28.0, under a password, and
|
|
65
|
-
* nothing in this browser can open it. The page catches it by `code` and asks
|
|
66
|
-
* for the password once; the worker cannot ask anyone anything. */
|
|
67
|
-
export class KeyPasswordNeeded extends Error {
|
|
68
|
-
constructor() { super('this browser needs your account password to unlock the signing key'); }
|
|
69
|
-
code = 'key-password-needed';
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Open an envelope from before 1.28.0. Throws on a wrong password (AES-GCM
|
|
73
|
-
* auth failure) — which is how the page tells a typo from a real problem. */
|
|
74
|
-
export async function unwrapKeys(envelope, password) {
|
|
75
|
-
if (!envelope || envelope.v !== 1) throw new Error('not a key envelope');
|
|
76
|
-
const aes = await deriveAesKey(password, fromB64(envelope.salt), envelope.iterations);
|
|
77
|
-
let plaintext;
|
|
78
|
-
try {
|
|
79
|
-
plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: fromB64(envelope.iv) }, aes, fromB64(envelope.ct));
|
|
80
|
-
} catch { throw new Error('wrong password'); }
|
|
81
|
-
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
82
|
-
}
|
package/web/app/oidc-session.mjs
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// oidc-session.mjs — FediPod's Solid-OIDC session: the solid-oidc-session
|
|
2
|
-
// library (lib/session/) bound to this app's database and client name. The
|
|
3
|
-
// page and the service worker both import this file, so both read one session.
|
|
4
|
-
import { solidOidcSession } from '../../lib/session/oidc-session.mjs';
|
|
5
|
-
|
|
6
|
-
export const { beginLogin, completeLogin, getSession, signOut } = solidOidcSession({ dbName: 'fedipod-oidc', clientName: 'FediPod' });
|