fedipod-server 0.9.0 → 0.11.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/dist/claims.js +1 -1
- package/dist/handler.d.ts +1 -1
- package/dist/handler.js +35 -2
- package/dist/handler.jsonld +1 -1
- package/lib/admin.mjs +3 -3
- package/lib/c2s.mjs +27 -2
- package/lib/deliver.mjs +6 -0
- package/lib/embed.mjs +1 -0
- package/lib/export-collections.mjs +1 -1
- package/lib/front-core.mjs +272 -49
- package/lib/gateway-core.mjs +3 -7
- package/lib/httpsig.mjs +33 -2
- package/lib/import.mjs +1 -1
- package/lib/intake.mjs +357 -118
- package/lib/links.mjs +6 -32
- package/lib/mastoapi.mjs +449 -38
- package/lib/migrate.mjs +4 -18
- package/lib/pod/README.md +61 -0
- package/lib/pod/actor.mjs +96 -0
- package/lib/pod/collection.mjs +77 -0
- package/lib/pod/containers.mjs +136 -0
- package/lib/pod/discovery.mjs +46 -0
- package/lib/pod/featured.mjs +18 -0
- package/lib/pod/followers.mjs +24 -0
- package/lib/pod/following.mjs +10 -0
- package/lib/pod/http.mjs +74 -0
- package/lib/pod/inbox.mjs +144 -0
- package/lib/pod/links.mjs +35 -0
- package/lib/pod/media.mjs +27 -0
- package/lib/pod/notes.mjs +80 -0
- package/lib/pod/notifications.mjs +89 -0
- package/lib/pod/outbox.mjs +18 -0
- package/lib/pod/package.json +19 -0
- package/lib/pod/policy.mjs +58 -0
- package/lib/pod/private.mjs +19 -0
- package/lib/pod/root.mjs +63 -0
- package/lib/pod/state.mjs +39 -0
- package/lib/pod/transport.mjs +477 -0
- package/lib/pod/urls.mjs +71 -0
- package/lib/polls.mjs +105 -0
- package/lib/publisher.mjs +357 -188
- package/lib/remote.mjs +30 -395
- package/lib/social.mjs +20 -9
- package/lib/storage.mjs +2 -2
- package/lib/store.mjs +8 -1
- package/lib/tagfeed.mjs +19 -2
- package/lib/wire.mjs +87 -67
- package/package.json +1 -1
- package/run-agent.mjs +20 -39
- package/web/admin/admin.js +141 -74
- package/web/admin/bar.css +9 -13
- package/web/admin/bar.js +2 -2
- package/web/admin/client/client.js +1 -1
- package/web/admin/client/index.html +5 -6
- package/web/admin/index.html +53 -65
- package/web/admin/setup/index.html +2 -1
- package/web/admin/setup/setup.js +1 -1
- package/web/admin/tokens.css +56 -0
- package/web/app/README.md +77 -0
- package/web/app/admin-facade.mjs +549 -0
- package/web/app/agent.mjs +490 -0
- package/web/app/atproto-browser.mjs +86 -0
- package/web/app/boot.mjs +340 -0
- package/web/app/deliver-relay.mjs +72 -0
- package/web/app/dist/boot.js +34067 -0
- package/web/app/dist/boot.js.map +7 -0
- package/web/app/dist/sw.js +53745 -0
- package/web/app/dist/sw.js.map +7 -0
- package/web/app/fediacct-browser.mjs +146 -0
- package/web/app/idb-kv.mjs +35 -0
- package/web/app/index.html +203 -0
- package/web/app/index.html~ +152 -0
- package/web/app/keys-browser.mjs +46 -0
- package/web/app/keystore.mjs +99 -0
- package/web/app/oidc-session.mjs +189 -0
- package/web/app/pod-auth.mjs +248 -0
- package/web/app/pod-remote.mjs +84 -0
- package/web/app/shims/_globals.mjs +2 -0
- package/web/app/shims/fedify-sig.mjs +77 -0
- package/web/app/shims/node-crypto.mjs +113 -0
- package/web/app/shims/node-fs.mjs +17 -0
- package/web/app/shims/node-path.mjs +19 -0
- package/web/app/shims/node-url.mjs +7 -0
- package/web/app/shims/prelude.js +58 -0
- package/web/app/shims/safefetch.mjs +58 -0
- package/web/app/shims/web-push.mjs +14 -0
- package/web/app/signup.mjs +248 -0
- package/web/app/site/_headers +9 -0
- package/web/app/site/_redirects +21 -0
- package/web/app/site/admin/admin.js +1181 -0
- package/web/app/site/admin/bar.css +60 -0
- package/web/app/site/admin/bar.js +61 -0
- package/web/app/site/admin/client/client.js +66 -0
- package/web/app/site/admin/client/index.html +43 -0
- package/web/app/site/admin/index.html +556 -0
- package/web/app/site/admin/setup/index.html +191 -0
- package/web/app/site/admin/setup/setup.js +308 -0
- package/web/app/site/admin/tokens.css +56 -0
- package/web/app/site/admin/window.css +55 -0
- package/web/app/site/admin/window.js +136 -0
- package/web/app/site/app/404.html +40 -0
- package/web/app/site/app/_headers +2 -0
- package/web/app/site/app/apple-touch-icon.png +0 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js +2 -0
- package/web/app/site/app/assets/ICONS-DIPJeU0C.js.map +1 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js +183 -0
- package/web/app/site/app/assets/ScheduledAtField-D-Abfo4J.js.map +1 -0
- package/web/app/site/app/assets/Temml-CCjoacWt.woff2 +0 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js +2 -0
- package/web/app/site/app/assets/__vite-browser-external-C7iut881.js.map +1 -0
- package/web/app/site/app/assets/boosts-carousel-BiOaNdMT.jpg +0 -0
- package/web/app/site/app/assets/catch-up-O5q5C75Z.png +0 -0
- package/web/app/site/app/assets/chunk-aKtaBQYM.js +1 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js +2 -0
- package/web/app/site/app/assets/compose-CFEx7wb4.js.map +1 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js +35 -0
- package/web/app/site/app/assets/compose-ZJN5xSb4.js.map +1 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js +52 -0
- package/web/app/site/app/assets/debug.module-YFQgwnIr.js.map +1 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js +2 -0
- package/web/app/site/app/assets/dom-p1YcJ5Rw.js.map +1 -0
- package/web/app/site/app/assets/home-mobile-dark@2x-Dc-J8-SM.png +0 -0
- package/web/app/site/app/assets/home-mobile-light@2x-Bd3fod8m.png +0 -0
- package/web/app/site/app/assets/home-tablet-dark@2x-BgcNI8le.png +0 -0
- package/web/app/site/app/assets/home-tablet-light@2x-BW2RV9T6.png +0 -0
- package/web/app/site/app/assets/icons/add-circle-line-WD0mwORD.js +1 -0
- package/web/app/site/app/assets/icons/alert-line-CpFUMf9s.js +1 -0
- package/web/app/site/app/assets/icons/android-2-line-Bzxnrd3S.js +1 -0
- package/web/app/site/app/assets/icons/announcement-line-5nwaJFJm.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-circle-line-a7Id9-Iv.js +1 -0
- package/web/app/site/app/assets/icons/arrow-down-line-B8uzXzq6.js +1 -0
- package/web/app/site/app/assets/icons/arrow-left-line-CGrGT9C-.js +1 -0
- package/web/app/site/app/assets/icons/arrow-right-line-CE-HERfX.js +1 -0
- package/web/app/site/app/assets/icons/arrow-to-up-line-CvdQjmcy.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-circle-line-DEparSNF.js +1 -0
- package/web/app/site/app/assets/icons/arrow-up-line-C7yBeugc.js +1 -0
- package/web/app/site/app/assets/icons/arrows-right-line-BtbXmJGP.js +1 -0
- package/web/app/site/app/assets/icons/at-line-CF66Bznk.js +1 -0
- package/web/app/site/app/assets/icons/attachment-line-BY8It-l9.js +1 -0
- package/web/app/site/app/assets/icons/blockquote-line-BS6pU_hm.js +1 -0
- package/web/app/site/app/assets/icons/board-line-C0UWXyA6.js +1 -0
- package/web/app/site/app/assets/icons/bookmark-line-1CtX5JLF.js +1 -0
- package/web/app/site/app/assets/icons/building-5-line-Dd59MzbO.js +1 -0
- package/web/app/site/app/assets/icons/bus-2-line-6_dvA0GG.js +1 -0
- package/web/app/site/app/assets/icons/calendar-day-line-Crpl_fmu.js +1 -0
- package/web/app/site/app/assets/icons/calendar-month-line-vommts2C.js +1 -0
- package/web/app/site/app/assets/icons/calendar-time-add-line-BU_DTlQk.js +1 -0
- package/web/app/site/app/assets/icons/camera-line-Cv69MEei.js +1 -0
- package/web/app/site/app/assets/icons/celebrate-line-B8DvIoi0.js +1 -0
- package/web/app/site/app/assets/icons/chart-bar-line-FK-QOmtF.js +1 -0
- package/web/app/site/app/assets/icons/chart-line-line-BGcJAFWw.js +1 -0
- package/web/app/site/app/assets/icons/chat-3-line-YFK2-hwz.js +1 -0
- package/web/app/site/app/assets/icons/check-circle-line-DsAxFr5-.js +1 -0
- package/web/app/site/app/assets/icons/clipboard-line-BBp364gJ.js +1 -0
- package/web/app/site/app/assets/icons/close-circle-line-CICdiuGz.js +1 -0
- package/web/app/site/app/assets/icons/close-line-DYrFWcjv.js +1 -0
- package/web/app/site/app/assets/icons/cloud-line-tiQAbah8.js +1 -0
- package/web/app/site/app/assets/icons/code-line-fAl8iiV3.js +1 -0
- package/web/app/site/app/assets/icons/comment-2-line-CR3g7WeB.js +1 -0
- package/web/app/site/app/assets/icons/copy-2-line-CLW69yF5.js +1 -0
- package/web/app/site/app/assets/icons/delete-2-line-B8xmbr4n.js +1 -0
- package/web/app/site/app/assets/icons/document-3-line-DBGNhAg2.js +1 -0
- package/web/app/site/app/assets/icons/document-line-C7mb9sb7.js +1 -0
- package/web/app/site/app/assets/icons/down-line-BGqSSThd.js +1 -0
- package/web/app/site/app/assets/icons/edit-4-line-DcgGaEhx.js +1 -0
- package/web/app/site/app/assets/icons/emoji-2-line-D8YgnM-y.js +1 -0
- package/web/app/site/app/assets/icons/exit-line-Dc07BS-x.js +1 -0
- package/web/app/site/app/assets/icons/external-link-line-BvpSkmjO.js +1 -0
- package/web/app/site/app/assets/icons/eye-2-line-CiWXx-f_.js +1 -0
- package/web/app/site/app/assets/icons/eye-close-line-BgTaSJ4P.js +1 -0
- package/web/app/site/app/assets/icons/filter-2-line-B_mR2usy.js +1 -0
- package/web/app/site/app/assets/icons/filter-line-C_nm-3cU.js +1 -0
- package/web/app/site/app/assets/icons/flag-1-line-C2HB05I0.js +1 -0
- package/web/app/site/app/assets/icons/forbid-circle-line-D3LTT9yd.js +1 -0
- package/web/app/site/app/assets/icons/formula-line-DLrt2d6j.js +1 -0
- package/web/app/site/app/assets/icons/grid-line-CgUmEikf.js +1 -0
- package/web/app/site/app/assets/icons/group-line-C5fnX0wU.js +1 -0
- package/web/app/site/app/assets/icons/hand-finger-2-line-BGgxbuL9.js +1 -0
- package/web/app/site/app/assets/icons/hashtag-line-CyTEEXBb.js +1 -0
- package/web/app/site/app/assets/icons/heart-crack-line-BZddbO2C.js +1 -0
- package/web/app/site/app/assets/icons/heart-line-CJ7K47J4.js +1 -0
- package/web/app/site/app/assets/icons/history-2-line-kWWvFzqv.js +1 -0
- package/web/app/site/app/assets/icons/history-line-bGQiwbTH.js +1 -0
- package/web/app/site/app/assets/icons/home-3-line-fRU1zeLG.js +1 -0
- package/web/app/site/app/assets/icons/information-line-iNk6-sOY.js +1 -0
- package/web/app/site/app/assets/icons/keyboard-line-DsCyuQNz.js +1 -0
- package/web/app/site/app/assets/icons/layout-4-line-CZkOX52Z.js +1 -0
- package/web/app/site/app/assets/icons/layout-5-line-B_znt5wO.js +1 -0
- package/web/app/site/app/assets/icons/left-line-eUw0tya4.js +1 -0
- package/web/app/site/app/assets/icons/lightning-line-DaKoDdix.js +1 -0
- package/web/app/site/app/assets/icons/link-2-line-DCqLmMA7.js +1 -0
- package/web/app/site/app/assets/icons/list-check-line-BAxosBh9.js +1 -0
- package/web/app/site/app/assets/icons/lock-line-CPoaXeUW.js +1 -0
- package/web/app/site/app/assets/icons/mail-line-B0P8aa1L.js +1 -0
- package/web/app/site/app/assets/icons/moon-line-CEqNAToP.js +1 -0
- package/web/app/site/app/assets/icons/more-1-fill-Dw2e5xab.js +1 -0
- package/web/app/site/app/assets/icons/more-3-line-BIiVjYTz.js +1 -0
- package/web/app/site/app/assets/icons/notification-line-BZZq2Gtu.js +1 -0
- package/web/app/site/app/assets/icons/pencil-line-CKh8-A1v.js +1 -0
- package/web/app/site/app/assets/icons/photo-album-line-Bnfg2u40.js +1 -0
- package/web/app/site/app/assets/icons/pin-line-DpTolyhs.js +1 -0
- package/web/app/site/app/assets/icons/play-fill-B0-G_8lX.js +1 -0
- package/web/app/site/app/assets/icons/qrcode-2-line-B6SPCaHn.js +1 -0
- package/web/app/site/app/assets/icons/quill-pen-line-B6LUrnCP.js +1 -0
- package/web/app/site/app/assets/icons/quote-left-fill-hQZ1cj_6.js +1 -0
- package/web/app/site/app/assets/icons/radar-line-CKyroTth.js +1 -0
- package/web/app/site/app/assets/icons/react-line-CoIKhhtK.js +1 -0
- package/web/app/site/app/assets/icons/refresh-2-line-D0mTF97S.js +1 -0
- package/web/app/site/app/assets/icons/right-line-B3KWJaqc.js +1 -0
- package/web/app/site/app/assets/icons/rocket-line-4KZl537D.js +1 -0
- package/web/app/site/app/assets/icons/round-fill-C69NTrZ0.js +1 -0
- package/web/app/site/app/assets/icons/round-line-B9k_1uo-.js +1 -0
- package/web/app/site/app/assets/icons/route-line-B28ReHcE.js +1 -0
- package/web/app/site/app/assets/icons/rows-4-line-CrBniagM.js +1 -0
- package/web/app/site/app/assets/icons/scan-line-DSf74YdP.js +1 -0
- package/web/app/site/app/assets/icons/search-2-line-B6geIU1z.js +1 -0
- package/web/app/site/app/assets/icons/settings-3-line-azJwK47w.js +1 -0
- package/web/app/site/app/assets/icons/settings-6-line-C_5zc5YR.js +1 -0
- package/web/app/site/app/assets/icons/share-2-line-D5MxWjrm.js +1 -0
- package/web/app/site/app/assets/icons/share-forward-line-Cp61fZzZ.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-2-line-DYYyXTaW.js +1 -0
- package/web/app/site/app/assets/icons/sparkles-line-C2hY1s6I.js +1 -0
- package/web/app/site/app/assets/icons/time-line-BF3eet1v.js +1 -0
- package/web/app/site/app/assets/icons/transfer-4-line-oisfvTBw.js +1 -0
- package/web/app/site/app/assets/icons/translate-line-BFWol2Ce.js +1 -0
- package/web/app/site/app/assets/icons/unlock-line-X_x5vwv3.js +1 -0
- package/web/app/site/app/assets/icons/upload-3-line-DgzwUJeW.js +1 -0
- package/web/app/site/app/assets/icons/user-4-line-BVSynZCp.js +1 -0
- package/web/app/site/app/assets/icons/user-add-2-line-BlGS5Q8q.js +1 -0
- package/web/app/site/app/assets/icons/user-add-line-BbXdHG4z.js +1 -0
- package/web/app/site/app/assets/icons/user-edit-line-BFAHOV0X.js +1 -0
- package/web/app/site/app/assets/icons/user-follow-line-BXux0wSd.js +1 -0
- package/web/app/site/app/assets/icons/user-star-line-DdYlpHLU.js +1 -0
- package/web/app/site/app/assets/icons/user-warning-line-Cj43j0_E.js +1 -0
- package/web/app/site/app/assets/icons/user-x-line-DIDgNlPH.js +1 -0
- package/web/app/site/app/assets/icons/volume-line-Bs390KQt.js +1 -0
- package/web/app/site/app/assets/icons/volume-mute-line-DzlzIqFZ.js +1 -0
- package/web/app/site/app/assets/icons/walk-line-yNXfB-md.js +1 -0
- package/web/app/site/app/assets/icons/world-2-line-CenkGIwj.js +1 -0
- package/web/app/site/app/assets/icons/zoom-in-line-CaENUphf.js +1 -0
- package/web/app/site/app/assets/icons/zoom-out-line-BDNk0EWo.js +1 -0
- package/web/app/site/app/assets/instances-BNPptnn-.json +333 -0
- package/web/app/site/app/assets/locales/ar-SA-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/ca-ES-DHRW86Tz.js +1 -0
- package/web/app/site/app/assets/locales/cs-CZ-CKoqEuA1.js +1 -0
- package/web/app/site/app/assets/locales/de-DE-ByRPMRFy.js +1 -0
- package/web/app/site/app/assets/locales/eo-UY-CPC7wLCS.js +1 -0
- package/web/app/site/app/assets/locales/es-ES-dJa0Fd9t.js +1 -0
- package/web/app/site/app/assets/locales/eu-ES-C3djlPR2.js +1 -0
- package/web/app/site/app/assets/locales/fa-IR-DTT490Ck.js +1 -0
- package/web/app/site/app/assets/locales/fi-FI-B23MCPuz.js +1 -0
- package/web/app/site/app/assets/locales/fr-FR-BcaQsO5C.js +1 -0
- package/web/app/site/app/assets/locales/gl-ES-BR-CVKBN.js +1 -0
- package/web/app/site/app/assets/locales/he-IL-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/hu-HU-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/it-IT-C1136UFV.js +1 -0
- package/web/app/site/app/assets/locales/ja-JP-CRZmRAhD.js +1 -0
- package/web/app/site/app/assets/locales/kab-Cz8xH_TT.js +1 -0
- package/web/app/site/app/assets/locales/ko-KR-CG9UiY4o.js +1 -0
- package/web/app/site/app/assets/locales/lt-LT-CJcI6sHZ.js +1 -0
- package/web/app/site/app/assets/locales/nb-NO-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/nl-NL-DxN4Yc1t.js +1 -0
- package/web/app/site/app/assets/locales/oc-FR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/pl-PL-DkwGXf6P.js +1 -0
- package/web/app/site/app/assets/locales/pseudo-LOCALE-CQ4XxkQ0.js +1 -0
- package/web/app/site/app/assets/locales/pt-BR-nGL0HdFS.js +1 -0
- package/web/app/site/app/assets/locales/pt-PT-Cp17aj2z.js +1 -0
- package/web/app/site/app/assets/locales/ru-RU-0Y8cIoaK.js +1 -0
- package/web/app/site/app/assets/locales/th-TH-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tok-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/tr-TR-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/locales/uk-UA-C5E3dFBf.js +1 -0
- package/web/app/site/app/assets/locales/zh-CN-BHJi7_et.js +1 -0
- package/web/app/site/app/assets/locales/zh-TW-CfheRbCu.js +1 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js +26 -0
- package/web/app/site/app/assets/main-BdqNbG-a.js.map +1 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js +2 -0
- package/web/app/site/app/assets/mock-home-DGhR__He.js.map +1 -0
- package/web/app/site/app/assets/multi-column-ROck0NVt.jpg +0 -0
- package/web/app/site/app/assets/multi-hashtag-timeline-DH5INVHi.jpg +0 -0
- package/web/app/site/app/assets/nested-comments-thread-hv59kZLc.jpg +0 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js +2 -0
- package/web/app/site/app/assets/polyfill-force-H8sUkLGp.js.map +1 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js +20 -0
- package/web/app/site/app/assets/pwa-viewport-ogKbF51K.js.map +1 -0
- package/web/app/site/app/assets/style-B1iLPTPR.css +2 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js +97 -0
- package/web/app/site/app/assets/temml-DuyIQMei.js.map +1 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js +2 -0
- package/web/app/site/app/assets/tinyld.light.node-DtbBhLYC.js.map +1 -0
- package/web/app/site/app/assets/year-in-posts-B2lywqdx.png +0 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js +3 -0
- package/web/app/site/app/assets/year-in-posts-DJv_star.js.map +1 -0
- package/web/app/site/app/compose/index.html +20 -0
- package/web/app/site/app/favicon.ico +0 -0
- package/web/app/site/app/index.html +73 -0
- package/web/app/site/app/logo-192.png +0 -0
- package/web/app/site/app/logo-512.png +0 -0
- package/web/app/site/app/logo-badge-72.png +0 -0
- package/web/app/site/app/logo-maskable-512.png +0 -0
- package/web/app/site/app/logo-monochrome-512.png +0 -0
- package/web/app/site/app/logo-monochrome-maskable-512.png +0 -0
- package/web/app/site/app/manifest.webmanifest +1 -0
- package/web/app/site/app/og-image-2.jpg +0 -0
- package/web/app/site/app/og-image.png +0 -0
- package/web/app/site/app/robots.txt +2 -0
- package/web/app/site/app/version.json +1 -0
- package/web/app/site/boot.js +34067 -0
- package/web/app/site/index.html +203 -0
- package/web/app/site/sw.js +53745 -0
- package/web/app/sw-src.mjs +205 -0
- package/web/front/#new-account.html# +43 -0
- package/web/front/admin.html +1 -81
- package/web/front/admin.js +85 -0
- package/web/front/new-account.html +1 -9
- package/web/front/new-account.js +13 -0
- package/web/front/run.html +1 -92
- package/web/front/run.js +107 -0
- package/lib/podrdf.mjs +0 -132
|
@@ -0,0 +1,146 @@
|
|
|
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/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
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
<style>
|
|
9
|
+
:root {
|
|
10
|
+
color-scheme: light dark;
|
|
11
|
+
font-size: 125%; /* 20px default (125% of 16) */
|
|
12
|
+
--bg: #ffffff; --fg: #1a1a1a;
|
|
13
|
+
--heading: #123f74; --sub: #565656; --line: #b0b0b0; --accent: #12467e;
|
|
14
|
+
--btn: #3a5f43; --btn-hover: #30503a; --btn-text: #ffffff;
|
|
15
|
+
--field-bg: #ececec; --field-text: #141414; --field-border: #6f6f6f;
|
|
16
|
+
--err: #b00020; --warn: #8a4b00; --focus: #12467e;
|
|
17
|
+
}
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
:root {
|
|
20
|
+
--bg: #191919; --fg: #f3f3f3;
|
|
21
|
+
--heading: #8fc2f2; --sub: #d2d2d2; --line: #6d6d6d; --accent: #8fc2f2;
|
|
22
|
+
--field-bg: #474747; --field-text: #f5f5f5; --field-border: #9aa0a6;
|
|
23
|
+
--btn: #4a7a54; --btn-hover: #3f6a49;
|
|
24
|
+
--err: #ff9b9b; --warn: #ffb366; --focus: #8fc2f2;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
body { font: 1rem/1.55 system-ui, sans-serif; margin: 0; background: var(--bg); color: var(--fg); }
|
|
28
|
+
#page { max-width: 42rem; margin: 0 auto; padding: 1.75rem 1rem 4rem; box-sizing: border-box; }
|
|
29
|
+
h1, h2, h3 { color: var(--heading); }
|
|
30
|
+
h1 { font-size: 1.6rem; margin: 0 0 .5rem; }
|
|
31
|
+
#brand { margin: 0 0 1.25rem; }
|
|
32
|
+
h2 { font-size: 1.2rem; margin: 2rem 0 .5rem; }
|
|
33
|
+
p { margin: .6rem 0; }
|
|
34
|
+
code { background: rgba(120,120,120,.18); padding: .05em .35em; border-radius: 4px; }
|
|
35
|
+
.sub { color: var(--sub); margin-top: 0; }
|
|
36
|
+
.hint { color: var(--sub); font-size: .95rem; margin: .15rem 0 0; } /* ~17px, never under 16 */
|
|
37
|
+
.grouplabel { display: block; margin: .75rem 0 .2rem; font-weight: 600; }
|
|
38
|
+
fieldset { border: 1px solid var(--line); border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
|
|
39
|
+
legend { padding: 0 .3rem; font-weight: 600; }
|
|
40
|
+
label { display: block; margin: .75rem 0 .2rem; }
|
|
41
|
+
input[type=text], input[type=email], input[type=url], input[type=password], select {
|
|
42
|
+
font: inherit; font-size: 1rem; width: 100%; padding: .55rem; box-sizing: border-box;
|
|
43
|
+
border: 1px solid var(--field-border); border-radius: .3rem;
|
|
44
|
+
background: var(--field-bg); color: var(--field-text); }
|
|
45
|
+
::placeholder { color: var(--sub); opacity: 1; }
|
|
46
|
+
.choice { display: block; margin: .4rem 0; font-weight: normal; }
|
|
47
|
+
.choice input { margin-right: .5rem; width: auto; }
|
|
48
|
+
button { font: inherit; font-size: 1rem; padding: .6rem 1.3rem; border-radius: .4rem;
|
|
49
|
+
border: 1px solid var(--btn); background: var(--btn); color: var(--btn-text);
|
|
50
|
+
font-weight: 600; cursor: pointer; }
|
|
51
|
+
button:hover { background: var(--btn-hover); border-color: var(--btn-hover); }
|
|
52
|
+
button.primary { font-weight: 700; }
|
|
53
|
+
button:disabled { opacity: .55; cursor: default; }
|
|
54
|
+
#create { margin-bottom: 2rem; }
|
|
55
|
+
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
|
|
56
|
+
.row { display: flex; gap: .5rem; align-items: center; }
|
|
57
|
+
.row input { flex: 1; }
|
|
58
|
+
.row button { white-space: nowrap; }
|
|
59
|
+
.address { font-size: 1.25rem; font-weight: 700; word-break: break-all; color: var(--fg); }
|
|
60
|
+
.warn { border-left: .25rem solid var(--warn); padding-left: .75rem; color: var(--warn); }
|
|
61
|
+
.err { border-left: .25rem solid var(--err); padding-left: .75rem; color: var(--err); }
|
|
62
|
+
.err:empty { display: none; }
|
|
63
|
+
.steps { list-style: none; padding: 0; }
|
|
64
|
+
.steps li { padding: .3rem 0; }
|
|
65
|
+
a { color: var(--accent); }
|
|
66
|
+
[hidden] { display: none !important; }
|
|
67
|
+
</style>
|
|
68
|
+
</head>
|
|
69
|
+
<body>
|
|
70
|
+
<main id="page">
|
|
71
|
+
|
|
72
|
+
<!-- The marketing hero: shown on the landing, hidden once a form is up. -->
|
|
73
|
+
<header id="hero">
|
|
74
|
+
<h1>FediPod - join the Fediverse from a Solid pod.</h1>
|
|
75
|
+
<p>Welcome to <b>fedipod.net</b>! By creating an account here, you can get a free Solid pod
|
|
76
|
+
and Fediverse identity or link to your existing pod/identity. Your fedipod.net
|
|
77
|
+
account provides you a portal to the Fediverse and filters unverified mail.</p>
|
|
78
|
+
|
|
79
|
+
<p>There is nothing to install, once you've setup an account, just point any browser at
|
|
80
|
+
<code>https://fedipod.net</code> and sign in.</p>
|
|
81
|
+
</header>
|
|
82
|
+
|
|
83
|
+
<!-- The short brand: shown only when a form is up, so the form stands alone. -->
|
|
84
|
+
<h1 id="brand" hidden>FediPod</h1>
|
|
85
|
+
|
|
86
|
+
<p id="loading" role="status">Loading…</p>
|
|
87
|
+
|
|
88
|
+
<section id="landing" hidden>
|
|
89
|
+
<h2>Already have a FediPod account?</h2>
|
|
90
|
+
<p class="row">
|
|
91
|
+
<input type="text" id="signin-address" autocapitalize="off" spellcheck="false" placeholder="@me@mypod"
|
|
92
|
+
aria-label="Your FediPod address, in the form @you@yourpod">
|
|
93
|
+
<button type="button" class="primary" id="signin">sign in</button>
|
|
94
|
+
</p>
|
|
95
|
+
<p class="err" id="signin-error" role="alert"></p>
|
|
96
|
+
<h2>New user?</h2>
|
|
97
|
+
<p><button type="button" class="primary" id="create">create an account</button></p>
|
|
98
|
+
<p>Fedipod.net is a FediPod Gateway operated by Jeff Zucker. You can detach from the gateway at any
|
|
99
|
+
time and transfer your account either to your own FediPod Gateway (easy to set up on a free tier Netlify)
|
|
100
|
+
or to any Fediverse server. All of your data lives on your pod and your Fediverse identity is yours
|
|
101
|
+
forever independent of which gateway you attach to.</p>
|
|
102
|
+
<p>For more information, see
|
|
103
|
+
<a href="https://github.com/jeff-zucker/FediPod">github.com/jeff-zucker/FediPod</a></p>
|
|
104
|
+
</section>
|
|
105
|
+
|
|
106
|
+
<!-- Shown only on a browser that has signed in but has never opened the signing
|
|
107
|
+
key. The key is kept on the pod encrypted under the account password, so
|
|
108
|
+
the pod's host cannot sign as you; a browser opens it once and remembers. -->
|
|
109
|
+
<section id="unlock" hidden>
|
|
110
|
+
<h2>Unlock your signing key</h2>
|
|
111
|
+
<p>Your signing key is stored on your pod, locked under your account password,
|
|
112
|
+
so nobody who can read your pod — your pod's host included — can post as you.
|
|
113
|
+
This browser has not opened it yet.</p>
|
|
114
|
+
<p class="row">
|
|
115
|
+
<input type="password" id="unlock-password" autocomplete="current-password"
|
|
116
|
+
placeholder="Your account password" aria-label="Your account password">
|
|
117
|
+
<button type="button" class="primary" id="unlock-go">unlock</button>
|
|
118
|
+
</p>
|
|
119
|
+
<p class="err" id="unlock-error" role="alert"></p>
|
|
120
|
+
<p class="hint">This browser only — it is not sent anywhere.</p>
|
|
121
|
+
</section>
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
<section id="pane-form" hidden>
|
|
125
|
+
<form id="form">
|
|
126
|
+
|
|
127
|
+
<!-- Screen 1: Your Solid Pod -->
|
|
128
|
+
<div class="step" id="step-1">
|
|
129
|
+
<fieldset>
|
|
130
|
+
<legend>Your Solid Pod</legend>
|
|
131
|
+
|
|
132
|
+
<div role="radiogroup" aria-labelledby="podtype-label">
|
|
133
|
+
<span id="podtype-label" class="grouplabel">Pod type</span>
|
|
134
|
+
<label class="choice"><input type="radio" name="mode" value="existing"> An existing pod</label>
|
|
135
|
+
<label class="choice"><input type="radio" name="mode" value="new" checked> A new pod</label>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<label for="provider">Pod provider</label>
|
|
139
|
+
<input type="url" id="provider" name="provider" autocomplete="url" placeholder="https://solidcommunity.net">
|
|
140
|
+
|
|
141
|
+
<label for="podName">Pod username/subdomain</label>
|
|
142
|
+
<input type="text" id="podName" name="podName" autocomplete="off" aria-describedby="podname-hint">
|
|
143
|
+
<p class="hint" id="podname-hint">The subdomain of your pod. On solidcommunity.net, alice
|
|
144
|
+
becomes the pod https://alice.solidcommunity.net.</p>
|
|
145
|
+
|
|
146
|
+
<label for="email">Pod email</label>
|
|
147
|
+
<input type="email" id="email" name="email" autocomplete="email" placeholder="you@example.org">
|
|
148
|
+
|
|
149
|
+
<label for="password">Pod password</label>
|
|
150
|
+
<input type="password" id="password" name="password" autocomplete="current-password"
|
|
151
|
+
aria-describedby="password-hint">
|
|
152
|
+
<p class="hint" id="password-hint">Your password is sent once, to make your account and to
|
|
153
|
+
lock your signing key. It is never stored. The credential used to set things up is
|
|
154
|
+
deleted when setup finishes — from then on this browser signs in through your pod,
|
|
155
|
+
the way any other Solid app does.</p>
|
|
156
|
+
</fieldset>
|
|
157
|
+
<p class="err" id="err-1" role="alert"></p>
|
|
158
|
+
<p>
|
|
159
|
+
<button type="button" id="cancel">Cancel</button>
|
|
160
|
+
<button type="button" class="primary" id="to-2">Continue</button>
|
|
161
|
+
</p>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<!-- Screen 2: Your Fediverse Identity -->
|
|
165
|
+
<div class="step" id="step-2" hidden>
|
|
166
|
+
<fieldset>
|
|
167
|
+
<legend>Your Fediverse Identity</legend>
|
|
168
|
+
|
|
169
|
+
<label for="handle">Fediverse handle</label>
|
|
170
|
+
<input type="text" id="handle" name="handle" autocomplete="off"
|
|
171
|
+
placeholder="the handle part of your address: @HANDLE@server" aria-describedby="handle-hint">
|
|
172
|
+
<p class="hint" id="handle-hint">The name in your Fediverse address. This is permanent.
|
|
173
|
+
Letters, digits, and hyphens only.</p>
|
|
174
|
+
|
|
175
|
+
<span id="addr-label" class="grouplabel">Your Fediverse address</span>
|
|
176
|
+
<p class="address" id="preview" role="status" aria-live="polite" aria-labelledby="addr-label">@…@…</p>
|
|
177
|
+
</fieldset>
|
|
178
|
+
<p class="hint">The handle and the pod cannot be changed afterwards. Everything else — display
|
|
179
|
+
name, bio, pictures — you set later in the client.</p>
|
|
180
|
+
<p class="err" id="form-error" role="alert"></p>
|
|
181
|
+
<p>
|
|
182
|
+
<button type="button" id="back-1">Back</button>
|
|
183
|
+
<button type="submit" class="primary" id="submit">Create Pod and Fediverse accounts</button>
|
|
184
|
+
</p>
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
</form>
|
|
188
|
+
</section>
|
|
189
|
+
|
|
190
|
+
<section id="running" hidden>
|
|
191
|
+
<h2 id="running-title">Setting up…</h2>
|
|
192
|
+
<ul class="steps" id="steps" aria-live="polite"></ul>
|
|
193
|
+
<p class="err" id="run-error" role="alert"></p>
|
|
194
|
+
<p id="run-actions" hidden>
|
|
195
|
+
<button type="button" id="run-back">Start over</button>
|
|
196
|
+
<button type="button" class="primary" id="run-retry">Try again</button>
|
|
197
|
+
</p>
|
|
198
|
+
</section>
|
|
199
|
+
|
|
200
|
+
</main>
|
|
201
|
+
<script type="module" src="/boot.js"></script>
|
|
202
|
+
</body>
|
|
203
|
+
</html>
|
|
@@ -0,0 +1,152 @@
|
|
|
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://solidcommunity.net https://*.solidcommunity.net; form-action 'self' https://solidcommunity.net https://*.solidcommunity.net; base-uri 'none'">
|
|
7
|
+
<title>FediPod</title>
|
|
8
|
+
<style>
|
|
9
|
+
:root { color-scheme: light dark; font-size: 112.5%; --heading: #1a4f8a; --sub: #666; --line: #bbb; --accent: #1a4f8a; }
|
|
10
|
+
@media (prefers-color-scheme: dark) { :root { --heading: #7fb3e8; --sub: #aaa; --line: #555; --accent: #7fb3e8; } }
|
|
11
|
+
body { font: 1rem/1.5 system-ui, sans-serif; margin: 0; }
|
|
12
|
+
#page { max-width: 42rem; margin: 0 auto; padding: 1.75rem 1rem 4rem; box-sizing: border-box; }
|
|
13
|
+
h1, h2, h3 { color: var(--heading); }
|
|
14
|
+
h1 { font-size: 1.5rem; margin: 0 0 .5rem; }
|
|
15
|
+
#brand { margin: 0 0 1.25rem; }
|
|
16
|
+
h2 { font-size: 1.1rem; margin: 2rem 0 .5rem; }
|
|
17
|
+
p { margin: .6rem 0; }
|
|
18
|
+
code { background: rgba(120,120,120,.14); padding: .05em .35em; border-radius: 4px; }
|
|
19
|
+
.sub { color: var(--sub); margin-top: 0; }
|
|
20
|
+
.hint { color: var(--sub); font-size: .9rem; margin: .15rem 0 0; }
|
|
21
|
+
fieldset { border: 1px solid var(--line); border-radius: .4rem; margin: 1rem 0; padding: .75rem 1rem 1rem; }
|
|
22
|
+
legend { padding: 0 .3rem; font-weight: 600; }
|
|
23
|
+
label { display: block; margin: .75rem 0 .2rem; }
|
|
24
|
+
input[type=text], input[type=email], input[type=url], input[type=password], select {
|
|
25
|
+
font: inherit; width: 100%; padding: .5rem; box-sizing: border-box;
|
|
26
|
+
border: 1px solid #767676; border-radius: .3rem; background: Field; color: FieldText; }
|
|
27
|
+
.choice { display: block; margin: .35rem 0; font-weight: normal; }
|
|
28
|
+
.choice input { margin-right: .4rem; width: auto; }
|
|
29
|
+
button { font: inherit; padding: .6rem 1.2rem; border-radius: .3rem; border: 1px solid #666;
|
|
30
|
+
background: ButtonFace; color: ButtonText; cursor: pointer; }
|
|
31
|
+
button.primary { font-weight: 600; }
|
|
32
|
+
button:disabled { opacity: .5; cursor: default; }
|
|
33
|
+
.row { display: flex; gap: .5rem; align-items: center; }
|
|
34
|
+
.row input { flex: 1; }
|
|
35
|
+
.row button { white-space: nowrap; }
|
|
36
|
+
.address { font-size: 1.2rem; font-weight: 600; word-break: break-all; }
|
|
37
|
+
.warn { border-left: .25rem solid #c60; padding-left: .75rem; }
|
|
38
|
+
.err { border-left: .25rem solid #b00020; padding-left: .75rem; color: #b00020; }
|
|
39
|
+
@media (prefers-color-scheme: dark) { .err { color: #ff8a8a; border-left-color: #ff8a8a; } }
|
|
40
|
+
#form-error:empty, #signin-error:empty { display: none; }
|
|
41
|
+
.steps { list-style: none; padding: 0; }
|
|
42
|
+
.steps li { padding: .3rem 0; }
|
|
43
|
+
a { color: var(--accent); }
|
|
44
|
+
[hidden] { display: none !important; }
|
|
45
|
+
</style>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<main id="page">
|
|
49
|
+
|
|
50
|
+
<!-- The marketing hero: shown on the landing, hidden once a form is up. -->
|
|
51
|
+
<header id="hero">
|
|
52
|
+
<h1>FediPod - join the Fediverse from a Solid pod.</h1>
|
|
53
|
+
<p>Welcome to <b>fedipod.net</b>! By creating an account here, you can get a free Solid pod
|
|
54
|
+
and Fediverse identity or link to your existing pod/identity. Your <code>fedipod.net</code>
|
|
55
|
+
account provides you a portal to the Fediverse and filters unverified mail but does not tie
|
|
56
|
+
you to anything. All of your data lives on your pod and your Fediverse identity is yours
|
|
57
|
+
forever regardless of what happens to fedipod.net.</p>
|
|
58
|
+
<p>There is nothing to install, once you've setup an account, just point any browser at
|
|
59
|
+
<code>https://fedipod.net</code> and sign in.</p>
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
<!-- The short brand: shown only when a form is up, so the form stands alone. -->
|
|
63
|
+
<h1 id="brand" hidden>FediPod</h1>
|
|
64
|
+
|
|
65
|
+
<p id="loading">Loading…</p>
|
|
66
|
+
|
|
67
|
+
<section id="landing" hidden>
|
|
68
|
+
<h2>Already have an account?</h2>
|
|
69
|
+
<p class="row">
|
|
70
|
+
<input type="text" id="signin-address" autocapitalize="off" spellcheck="false" placeholder="@me@mypod">
|
|
71
|
+
<button type="button" class="primary" id="signin">sign in</button>
|
|
72
|
+
</p>
|
|
73
|
+
<p class="err" id="signin-error" role="alert"></p>
|
|
74
|
+
<h2>New user?</h2>
|
|
75
|
+
<p><button type="button" class="primary" id="create">create an account</button></p>
|
|
76
|
+
<p class="hint">For more information, see
|
|
77
|
+
<a href="https://github.com/jeff-zucker/FediPod">github.com/jeff-zucker/FediPod</a></p>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section id="pane-form" hidden>
|
|
81
|
+
<form id="form">
|
|
82
|
+
|
|
83
|
+
<!-- Screen 1: Your Solid Pod -->
|
|
84
|
+
<div class="step" id="step-1">
|
|
85
|
+
<fieldset>
|
|
86
|
+
<legend>Your Solid Pod</legend>
|
|
87
|
+
|
|
88
|
+
<label>Pod type</label>
|
|
89
|
+
<label class="choice"><input type="radio" name="mode" value="existing"> An existing pod</label>
|
|
90
|
+
<label class="choice"><input type="radio" name="mode" value="new" checked> A new pod</label>
|
|
91
|
+
|
|
92
|
+
<label for="provider">Pod provider</label>
|
|
93
|
+
<input type="url" id="provider" name="provider" autocomplete="url" placeholder="https://solidcommunity.net">
|
|
94
|
+
|
|
95
|
+
<label for="podName">Pod username/subdomain</label>
|
|
96
|
+
<input type="text" id="podName" name="podName" autocomplete="off" aria-describedby="podname-hint">
|
|
97
|
+
<p class="hint" id="podname-hint">The subdomain of your pod: your pod address becomes
|
|
98
|
+
https://YOUR-USERNAME.your-provider.</p>
|
|
99
|
+
|
|
100
|
+
<label for="email">Pod email</label>
|
|
101
|
+
<input type="email" id="email" name="email" autocomplete="email" placeholder="you@example.org">
|
|
102
|
+
|
|
103
|
+
<label for="password">Pod password</label>
|
|
104
|
+
<input type="password" id="password" name="password" autocomplete="current-password"
|
|
105
|
+
aria-describedby="password-hint">
|
|
106
|
+
<p class="hint" id="password-hint">The password is sent once in return for a credential stored
|
|
107
|
+
in this browser. The password itself is never stored, and you can revoke the credential
|
|
108
|
+
without changing it.</p>
|
|
109
|
+
</fieldset>
|
|
110
|
+
<p class="err" id="err-1" role="alert"></p>
|
|
111
|
+
<p>
|
|
112
|
+
<button type="button" id="cancel">Cancel</button>
|
|
113
|
+
<button type="button" class="primary" id="to-2">Continue</button>
|
|
114
|
+
</p>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
117
|
+
<!-- Screen 2: Your Fediverse Identity -->
|
|
118
|
+
<div class="step" id="step-2" hidden>
|
|
119
|
+
<fieldset>
|
|
120
|
+
<legend>Your Fediverse Identity</legend>
|
|
121
|
+
|
|
122
|
+
<label for="handle">Fediverse handle</label>
|
|
123
|
+
<input type="text" id="handle" name="handle" autocomplete="off"
|
|
124
|
+
placeholder="the handle part of your address: @HANDLE@server" aria-describedby="handle-hint">
|
|
125
|
+
<p class="hint" id="handle-hint">The name in your Fediverse address. This is permanent.
|
|
126
|
+
Letters, digits, and hyphens only.</p>
|
|
127
|
+
|
|
128
|
+
<label>Your Fediverse address</label>
|
|
129
|
+
<p class="address" id="preview">@…@…</p>
|
|
130
|
+
</fieldset>
|
|
131
|
+
<p class="hint">The handle and the pod cannot be changed afterwards. Everything else — display
|
|
132
|
+
name, bio, pictures — you set later in the client.</p>
|
|
133
|
+
<p class="err" id="form-error" role="alert"></p>
|
|
134
|
+
<p>
|
|
135
|
+
<button type="button" id="back-1">Back</button>
|
|
136
|
+
<button type="submit" class="primary" id="submit">Create Pod and Fediverse accounts</button>
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
</form>
|
|
141
|
+
</section>
|
|
142
|
+
|
|
143
|
+
<section id="running" hidden>
|
|
144
|
+
<h2>Setting up…</h2>
|
|
145
|
+
<ul class="steps" id="steps"></ul>
|
|
146
|
+
<p class="err" id="run-error" role="alert"></p>
|
|
147
|
+
</section>
|
|
148
|
+
|
|
149
|
+
</main>
|
|
150
|
+
<script type="module" src="/boot.js"></script>
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
import { kvGet, kvPut } from './idb-kv.mjs';
|
|
8
|
+
import * as podState from '../../lib/pod/state.mjs';
|
|
9
|
+
import { isKeyEnvelope, KeyPasswordNeeded } from './keystore.mjs';
|
|
10
|
+
|
|
11
|
+
const pemToDer = (pem) => Uint8Array.from(
|
|
12
|
+
atob(pem.replace(/-----[^-]+-----/g, '').replace(/\s+/g, '')), (c) => c.charCodeAt(0));
|
|
13
|
+
|
|
14
|
+
export async function importSigningKey(keysRecord) {
|
|
15
|
+
const rsaPrivate = await crypto.subtle.importKey('pkcs8', pemToDer(keysRecord.rsa.privatePem),
|
|
16
|
+
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['sign']);
|
|
17
|
+
return { rsaPrivate, rsaPublicPem: keysRecord.rsa.publicPem, edPrivate: null, edPublicMultibase: null };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Where this browser keeps its own opened copy of the signing key. The pod's
|
|
21
|
+
// copy is wrapped under the account password; this one is not, because it never
|
|
22
|
+
// leaves the origin — the same protection the OIDC session in the next IDB row
|
|
23
|
+
// already relies on. It is what lets the worker boot itself after an idle kill
|
|
24
|
+
// without a soul around to type anything.
|
|
25
|
+
export const keyCacheKey = (actorUrl) => `signing-keys:${actorUrl}`;
|
|
26
|
+
|
|
27
|
+
// Read the signing key for this actor and import it for signing.
|
|
28
|
+
//
|
|
29
|
+
// Two places hold one: this browser (opened, in IndexedDB) and the pod (wrapped).
|
|
30
|
+
// The browser's is tried first — it is the only one that can be read with no
|
|
31
|
+
// password, and a service worker has nobody to ask. Falling through to the pod
|
|
32
|
+
// covers the browser that has just signed in and has no copy yet: a bare record
|
|
33
|
+
// there is from before wrapping and is used as it stands, while an envelope
|
|
34
|
+
// needs the password and so has to go back to the page.
|
|
35
|
+
export async function loadKeysFromPod(remote, urls) {
|
|
36
|
+
const cached = await kvGet(keyCacheKey(urls.actor)).catch(() => null);
|
|
37
|
+
if (cached?.rsa) return importSigningKey(cached);
|
|
38
|
+
|
|
39
|
+
const doc = await podState.readWrappedKeys(remote, urls);
|
|
40
|
+
if (isKeyEnvelope(doc)) throw new KeyPasswordNeeded();
|
|
41
|
+
if (!doc || !doc.rsa) throw new Error('no signing key on the pod — sign up did not finish');
|
|
42
|
+
// A pre-wrapping install. Cache it so the next boot is one read, and leave
|
|
43
|
+
// the pod's copy alone: re-wrapping it would need the password we do not have.
|
|
44
|
+
await kvPut(keyCacheKey(urls.actor), doc).catch(() => {});
|
|
45
|
+
return importSigningKey(doc);
|
|
46
|
+
}
|