Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm

Merge branch 'pocket'

lakdsjflkj

+1 -1
.github/workflows/checks.yml
··· 28 - name: get nightly toolchain for jetstream fmt 29 run: rustup toolchain install nightly --allow-downgrade -c rustfmt 30 - name: fmt 31 - run: cargo fmt --package links --package constellation --package ufos --package spacedust --package who-am-i --package slingshot -- --check 32 - name: fmt jetstream (nightly) 33 run: cargo +nightly fmt --package jetstream -- --check 34 - name: clippy
··· 28 - name: get nightly toolchain for jetstream fmt 29 run: rustup toolchain install nightly --allow-downgrade -c rustfmt 30 - name: fmt 31 + run: cargo fmt --package links --package constellation --package ufos --package spacedust --package who-am-i --package slingshot --package pocket -- --check 32 - name: fmt jetstream (nightly) 33 run: cargo +nightly fmt --package jetstream -- --check 34 - name: clippy
+277 -77
Cargo.lock
··· 192 "nom", 193 "num-traits", 194 "rusticata-macros", 195 - "thiserror 2.0.12", 196 "time", 197 ] 198 ··· 371 ] 372 373 [[package]] 374 name = "atrium-identity" 375 version = "0.1.5" 376 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 628 "axum", 629 "handlebars", 630 "serde", 631 - "thiserror 2.0.12", 632 ] 633 634 [[package]] ··· 774 ] 775 776 [[package]] 777 name = "bitflags" 778 version = "2.9.0" 779 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 907 ] 908 909 [[package]] 910 name = "cid" 911 version = "0.11.1" 912 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 933 934 [[package]] 935 name = "clap" 936 - version = "4.5.46" 937 source = "registry+https://github.com/rust-lang/crates.io-index" 938 - checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" 939 dependencies = [ 940 "clap_builder", 941 "clap_derive", ··· 943 944 [[package]] 945 name = "clap_builder" 946 - version = "4.5.46" 947 source = "registry+https://github.com/rust-lang/crates.io-index" 948 - checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" 949 dependencies = [ 950 "anstream", 951 "anstyle", ··· 955 956 [[package]] 957 name = "clap_derive" 958 - version = "4.5.45" 959 source = "registry+https://github.com/rust-lang/crates.io-index" 960 - checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" 961 dependencies = [ 962 "heck", 963 "proc-macro2", ··· 1170 version = "0.8.21" 1171 source = "registry+https://github.com/rust-lang/crates.io-index" 1172 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 1173 1174 [[package]] 1175 name = "crypto-bigint" ··· 1514 "slog-bunyan", 1515 "slog-json", 1516 "slog-term", 1517 - "thiserror 2.0.12", 1518 "tokio", 1519 "tokio-rustls 0.25.0", 1520 "toml", ··· 1684 ] 1685 1686 [[package]] 1687 name = "fastrand" 1688 version = "2.3.0" 1689 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1814 "mixtrics", 1815 "pin-project", 1816 "serde", 1817 - "thiserror 2.0.12", 1818 "tokio", 1819 "tracing", 1820 ] ··· 1834 "parking_lot", 1835 "pin-project", 1836 "serde", 1837 - "thiserror 2.0.12", 1838 "tokio", 1839 "twox-hash", 1840 ] ··· 1867 "parking_lot", 1868 "pin-project", 1869 "serde", 1870 - "thiserror 2.0.12", 1871 "tokio", 1872 "tracing", 1873 ] ··· 1899 "pin-project", 1900 "rand 0.9.1", 1901 "serde", 1902 - "thiserror 2.0.12", 1903 "tokio", 1904 "tracing", 1905 "twox-hash", ··· 2121 ] 2122 2123 [[package]] 2124 name = "handlebars" 2125 version = "6.3.2" 2126 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2133 "pest_derive", 2134 "serde", 2135 "serde_json", 2136 - "thiserror 2.0.12", 2137 "walkdir", 2138 ] 2139 ··· 2167 "allocator-api2", 2168 "equivalent", 2169 "foldhash", 2170 ] 2171 2172 [[package]] ··· 2223 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 2224 2225 [[package]] 2226 name = "hickory-proto" 2227 version = "0.25.2" 2228 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2240 "once_cell", 2241 "rand 0.9.1", 2242 "ring", 2243 - "thiserror 2.0.12", 2244 "tinyvec", 2245 "tokio", 2246 "tracing", ··· 2263 "rand 0.9.1", 2264 "resolv-conf", 2265 "smallvec", 2266 - "thiserror 2.0.12", 2267 "tokio", 2268 "tracing", 2269 ] ··· 2757 "metrics", 2758 "serde", 2759 "serde_json", 2760 - "thiserror 2.0.12", 2761 "tokio", 2762 "tokio-tungstenite 0.26.2", 2763 "url", ··· 2860 ] 2861 2862 [[package]] 2863 name = "langtag" 2864 version = "0.3.4" 2865 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2952 ] 2953 2954 [[package]] 2955 name = "libz-sys" 2956 version = "1.1.22" 2957 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2969 "anyhow", 2970 "fluent-uri", 2971 "nom", 2972 - "thiserror 2.0.12", 2973 "tinyjson", 2974 ] 2975 ··· 3003 3004 [[package]] 3005 name = "log" 3006 - version = "0.4.27" 3007 source = "registry+https://github.com/rust-lang/crates.io-index" 3008 - checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 3009 3010 [[package]] 3011 name = "loom" ··· 3155 3156 [[package]] 3157 name = "matchers" 3158 - version = "0.1.0" 3159 source = "registry+https://github.com/rust-lang/crates.io-index" 3160 - checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 3161 dependencies = [ 3162 - "regex-automata 0.1.10", 3163 ] 3164 3165 [[package]] ··· 3235 "metrics", 3236 "metrics-util 0.20.0", 3237 "quanta", 3238 - "thiserror 2.0.12", 3239 "tokio", 3240 "tracing", 3241 ] ··· 3458 3459 [[package]] 3460 name = "nu-ansi-term" 3461 - version = "0.46.0" 3462 source = "registry+https://github.com/rust-lang/crates.io-index" 3463 - checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 3464 dependencies = [ 3465 - "overload", 3466 - "winapi", 3467 ] 3468 3469 [[package]] ··· 3666 ] 3667 3668 [[package]] 3669 - name = "overload" 3670 - version = "0.1.1" 3671 - source = "registry+https://github.com/rust-lang/crates.io-index" 3672 - checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 3673 - 3674 - [[package]] 3675 name = "p256" 3676 version = "0.13.2" 3677 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3784 checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" 3785 dependencies = [ 3786 "memchr", 3787 - "thiserror 2.0.12", 3788 "ucd-trie", 3789 ] 3790 ··· 3881 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 3882 3883 [[package]] 3884 name = "poem" 3885 version = "3.1.12" 3886 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3918 "smallvec", 3919 "sync_wrapper", 3920 "tempfile", 3921 - "thiserror 2.0.12", 3922 "tokio", 3923 "tokio-rustls 0.26.2", 3924 "tokio-stream", ··· 3962 "serde_json", 3963 "serde_urlencoded", 3964 "serde_yaml", 3965 - "thiserror 2.0.12", 3966 "tokio", 3967 ] 3968 ··· 3981 "quote", 3982 "regex", 3983 "syn 2.0.103", 3984 - "thiserror 2.0.12", 3985 ] 3986 3987 [[package]] ··· 4130 "rustc-hash 2.1.1", 4131 "rustls 0.23.31", 4132 "socket2 0.5.9", 4133 - "thiserror 2.0.12", 4134 "tokio", 4135 "tracing", 4136 "web-time", ··· 4151 "rustls 0.23.31", 4152 "rustls-pki-types", 4153 "slab", 4154 - "thiserror 2.0.12", 4155 "tinyvec", 4156 "tracing", 4157 "web-time", ··· 4336 ] 4337 4338 [[package]] 4339 name = "regex" 4340 version = "1.11.1" 4341 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4343 dependencies = [ 4344 "aho-corasick", 4345 "memchr", 4346 - "regex-automata 0.4.9", 4347 - "regex-syntax 0.8.5", 4348 - ] 4349 - 4350 - [[package]] 4351 - name = "regex-automata" 4352 - version = "0.1.10" 4353 - source = "registry+https://github.com/rust-lang/crates.io-index" 4354 - checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 4355 - dependencies = [ 4356 - "regex-syntax 0.6.29", 4357 ] 4358 4359 [[package]] ··· 4364 dependencies = [ 4365 "aho-corasick", 4366 "memchr", 4367 - "regex-syntax 0.8.5", 4368 ] 4369 4370 [[package]] 4371 name = "regex-syntax" 4372 - version = "0.6.29" 4373 - source = "registry+https://github.com/rust-lang/crates.io-index" 4374 - checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 4375 - 4376 - [[package]] 4377 - name = "regex-syntax" 4378 version = "0.8.5" 4379 source = "registry+https://github.com/rust-lang/crates.io-index" 4380 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" ··· 4502 "spki", 4503 "subtle", 4504 "zeroize", 4505 ] 4506 4507 [[package]] ··· 4733 ] 4734 4735 [[package]] 4736 name = "security-framework" 4737 version = "2.11.1" 4738 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4865 "percent-encoding", 4866 "ryu", 4867 "serde", 4868 - "thiserror 2.0.12", 4869 ] 4870 4871 [[package]] ··· 5008 dependencies = [ 5009 "num-bigint", 5010 "num-traits", 5011 - "thiserror 2.0.12", 5012 "time", 5013 ] 5014 ··· 5050 "rustls 0.23.31", 5051 "serde", 5052 "serde_json", 5053 - "thiserror 2.0.12", 5054 "time", 5055 "tokio", 5056 "tokio-util", ··· 5161 "serde", 5162 "serde_json", 5163 "serde_qs", 5164 - "thiserror 2.0.12", 5165 "tinyjson", 5166 "tokio", 5167 "tokio-tungstenite 0.27.0", ··· 5327 5328 [[package]] 5329 name = "thiserror" 5330 - version = "2.0.12" 5331 source = "registry+https://github.com/rust-lang/crates.io-index" 5332 - checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 5333 dependencies = [ 5334 - "thiserror-impl 2.0.12", 5335 ] 5336 5337 [[package]] ··· 5347 5348 [[package]] 5349 name = "thiserror-impl" 5350 - version = "2.0.12" 5351 source = "registry+https://github.com/rust-lang/crates.io-index" 5352 - checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 5353 dependencies = [ 5354 "proc-macro2", 5355 "quote", ··· 5452 5453 [[package]] 5454 name = "tokio" 5455 - version = "1.47.0" 5456 source = "registry+https://github.com/rust-lang/crates.io-index" 5457 - checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" 5458 dependencies = [ 5459 "backtrace", 5460 "bytes", ··· 5695 5696 [[package]] 5697 name = "tracing-subscriber" 5698 - version = "0.3.19" 5699 source = "registry+https://github.com/rust-lang/crates.io-index" 5700 - checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 5701 dependencies = [ 5702 "matchers", 5703 "nu-ansi-term", 5704 "once_cell", 5705 - "regex", 5706 "sharded-slab", 5707 "smallvec", 5708 "thread_local", ··· 5742 "native-tls", 5743 "rand 0.9.1", 5744 "sha1", 5745 - "thiserror 2.0.12", 5746 "url", 5747 "utf-8", 5748 ] ··· 5760 "log", 5761 "rand 0.9.1", 5762 "sha1", 5763 - "thiserror 2.0.12", 5764 "utf-8", 5765 ] 5766 ··· 5813 "serde_qs", 5814 "sha2", 5815 "tempfile", 5816 - "thiserror 2.0.12", 5817 "tikv-jemallocator", 5818 "tokio", 5819 "tokio-util", ··· 6147 "reqwest", 6148 "serde", 6149 "serde_json", 6150 - "thiserror 2.0.12", 6151 "tokio", 6152 "tokio-util", 6153 "url", ··· 6540 "nom", 6541 "oid-registry", 6542 "rusticata-macros", 6543 - "thiserror 2.0.12", 6544 "time", 6545 ] 6546 ··· 6651 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 6652 dependencies = [ 6653 "serde", 6654 ] 6655 6656 [[package]]
··· 192 "nom", 193 "num-traits", 194 "rusticata-macros", 195 + "thiserror 2.0.16", 196 "time", 197 ] 198 ··· 371 ] 372 373 [[package]] 374 + name = "atrium-crypto" 375 + version = "0.1.2" 376 + source = "registry+https://github.com/rust-lang/crates.io-index" 377 + checksum = "73a3da430c71dd9006d61072c20771f264e5c498420a49c32305ceab8bd71955" 378 + dependencies = [ 379 + "ecdsa", 380 + "k256", 381 + "multibase", 382 + "p256", 383 + "thiserror 1.0.69", 384 + ] 385 + 386 + [[package]] 387 name = "atrium-identity" 388 version = "0.1.5" 389 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 641 "axum", 642 "handlebars", 643 "serde", 644 + "thiserror 2.0.16", 645 ] 646 647 [[package]] ··· 787 ] 788 789 [[package]] 790 + name = "bitcoin-io" 791 + version = "0.1.3" 792 + source = "registry+https://github.com/rust-lang/crates.io-index" 793 + checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" 794 + 795 + [[package]] 796 + name = "bitcoin_hashes" 797 + version = "0.14.0" 798 + source = "registry+https://github.com/rust-lang/crates.io-index" 799 + checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" 800 + dependencies = [ 801 + "bitcoin-io", 802 + "hex-conservative", 803 + ] 804 + 805 + [[package]] 806 name = "bitflags" 807 version = "2.9.0" 808 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 936 ] 937 938 [[package]] 939 + name = "ciborium" 940 + version = "0.2.2" 941 + source = "registry+https://github.com/rust-lang/crates.io-index" 942 + checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 943 + dependencies = [ 944 + "ciborium-io", 945 + "ciborium-ll", 946 + "serde", 947 + ] 948 + 949 + [[package]] 950 + name = "ciborium-io" 951 + version = "0.2.2" 952 + source = "registry+https://github.com/rust-lang/crates.io-index" 953 + checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 954 + 955 + [[package]] 956 + name = "ciborium-ll" 957 + version = "0.2.2" 958 + source = "registry+https://github.com/rust-lang/crates.io-index" 959 + checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 960 + dependencies = [ 961 + "ciborium-io", 962 + "half", 963 + ] 964 + 965 + [[package]] 966 name = "cid" 967 version = "0.11.1" 968 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 989 990 [[package]] 991 name = "clap" 992 + version = "4.5.47" 993 source = "registry+https://github.com/rust-lang/crates.io-index" 994 + checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931" 995 dependencies = [ 996 "clap_builder", 997 "clap_derive", ··· 999 1000 [[package]] 1001 name = "clap_builder" 1002 + version = "4.5.47" 1003 source = "registry+https://github.com/rust-lang/crates.io-index" 1004 + checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6" 1005 dependencies = [ 1006 "anstream", 1007 "anstyle", ··· 1011 1012 [[package]] 1013 name = "clap_derive" 1014 + version = "4.5.47" 1015 source = "registry+https://github.com/rust-lang/crates.io-index" 1016 + checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c" 1017 dependencies = [ 1018 "heck", 1019 "proc-macro2", ··· 1226 version = "0.8.21" 1227 source = "registry+https://github.com/rust-lang/crates.io-index" 1228 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 1229 + 1230 + [[package]] 1231 + name = "crunchy" 1232 + version = "0.2.4" 1233 + source = "registry+https://github.com/rust-lang/crates.io-index" 1234 + checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 1235 1236 [[package]] 1237 name = "crypto-bigint" ··· 1576 "slog-bunyan", 1577 "slog-json", 1578 "slog-term", 1579 + "thiserror 2.0.16", 1580 "tokio", 1581 "tokio-rustls 0.25.0", 1582 "toml", ··· 1746 ] 1747 1748 [[package]] 1749 + name = "fallible-iterator" 1750 + version = "0.3.0" 1751 + source = "registry+https://github.com/rust-lang/crates.io-index" 1752 + checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" 1753 + 1754 + [[package]] 1755 + name = "fallible-streaming-iterator" 1756 + version = "0.1.9" 1757 + source = "registry+https://github.com/rust-lang/crates.io-index" 1758 + checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 1759 + 1760 + [[package]] 1761 name = "fastrand" 1762 version = "2.3.0" 1763 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1888 "mixtrics", 1889 "pin-project", 1890 "serde", 1891 + "thiserror 2.0.16", 1892 "tokio", 1893 "tracing", 1894 ] ··· 1908 "parking_lot", 1909 "pin-project", 1910 "serde", 1911 + "thiserror 2.0.16", 1912 "tokio", 1913 "twox-hash", 1914 ] ··· 1941 "parking_lot", 1942 "pin-project", 1943 "serde", 1944 + "thiserror 2.0.16", 1945 "tokio", 1946 "tracing", 1947 ] ··· 1973 "pin-project", 1974 "rand 0.9.1", 1975 "serde", 1976 + "thiserror 2.0.16", 1977 "tokio", 1978 "tracing", 1979 "twox-hash", ··· 2195 ] 2196 2197 [[package]] 2198 + name = "half" 2199 + version = "2.6.0" 2200 + source = "registry+https://github.com/rust-lang/crates.io-index" 2201 + checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" 2202 + dependencies = [ 2203 + "cfg-if", 2204 + "crunchy", 2205 + ] 2206 + 2207 + [[package]] 2208 name = "handlebars" 2209 version = "6.3.2" 2210 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2217 "pest_derive", 2218 "serde", 2219 "serde_json", 2220 + "thiserror 2.0.16", 2221 "walkdir", 2222 ] 2223 ··· 2251 "allocator-api2", 2252 "equivalent", 2253 "foldhash", 2254 + ] 2255 + 2256 + [[package]] 2257 + name = "hashlink" 2258 + version = "0.10.0" 2259 + source = "registry+https://github.com/rust-lang/crates.io-index" 2260 + checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" 2261 + dependencies = [ 2262 + "hashbrown 0.15.2", 2263 ] 2264 2265 [[package]] ··· 2316 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 2317 2318 [[package]] 2319 + name = "hex-conservative" 2320 + version = "0.2.1" 2321 + source = "registry+https://github.com/rust-lang/crates.io-index" 2322 + checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" 2323 + dependencies = [ 2324 + "arrayvec", 2325 + ] 2326 + 2327 + [[package]] 2328 name = "hickory-proto" 2329 version = "0.25.2" 2330 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2342 "once_cell", 2343 "rand 0.9.1", 2344 "ring", 2345 + "thiserror 2.0.16", 2346 "tinyvec", 2347 "tokio", 2348 "tracing", ··· 2365 "rand 0.9.1", 2366 "resolv-conf", 2367 "smallvec", 2368 + "thiserror 2.0.16", 2369 "tokio", 2370 "tracing", 2371 ] ··· 2859 "metrics", 2860 "serde", 2861 "serde_json", 2862 + "thiserror 2.0.16", 2863 "tokio", 2864 "tokio-tungstenite 0.26.2", 2865 "url", ··· 2962 ] 2963 2964 [[package]] 2965 + name = "jwt-compact" 2966 + version = "0.9.0-beta.1" 2967 + source = "git+https://github.com/fatfingers23/jwt-compact.git#aed088b8ff5ad44ef2785c453f6a4b7916728b1c" 2968 + dependencies = [ 2969 + "anyhow", 2970 + "base64ct", 2971 + "chrono", 2972 + "ciborium", 2973 + "hmac", 2974 + "lazy_static", 2975 + "rand_core 0.6.4", 2976 + "secp256k1", 2977 + "serde", 2978 + "serde_json", 2979 + "sha2", 2980 + "smallvec", 2981 + "subtle", 2982 + "zeroize", 2983 + ] 2984 + 2985 + [[package]] 2986 + name = "k256" 2987 + version = "0.13.4" 2988 + source = "registry+https://github.com/rust-lang/crates.io-index" 2989 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 2990 + dependencies = [ 2991 + "cfg-if", 2992 + "ecdsa", 2993 + "elliptic-curve", 2994 + "sha2", 2995 + ] 2996 + 2997 + [[package]] 2998 name = "langtag" 2999 version = "0.3.4" 3000 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3087 ] 3088 3089 [[package]] 3090 + name = "libsqlite3-sys" 3091 + version = "0.35.0" 3092 + source = "registry+https://github.com/rust-lang/crates.io-index" 3093 + checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" 3094 + dependencies = [ 3095 + "pkg-config", 3096 + "vcpkg", 3097 + ] 3098 + 3099 + [[package]] 3100 name = "libz-sys" 3101 version = "1.1.22" 3102 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3114 "anyhow", 3115 "fluent-uri", 3116 "nom", 3117 + "thiserror 2.0.16", 3118 "tinyjson", 3119 ] 3120 ··· 3148 3149 [[package]] 3150 name = "log" 3151 + version = "0.4.28" 3152 source = "registry+https://github.com/rust-lang/crates.io-index" 3153 + checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" 3154 3155 [[package]] 3156 name = "loom" ··· 3300 3301 [[package]] 3302 name = "matchers" 3303 + version = "0.2.0" 3304 source = "registry+https://github.com/rust-lang/crates.io-index" 3305 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 3306 dependencies = [ 3307 + "regex-automata", 3308 ] 3309 3310 [[package]] ··· 3380 "metrics", 3381 "metrics-util 0.20.0", 3382 "quanta", 3383 + "thiserror 2.0.16", 3384 "tokio", 3385 "tracing", 3386 ] ··· 3603 3604 [[package]] 3605 name = "nu-ansi-term" 3606 + version = "0.50.1" 3607 source = "registry+https://github.com/rust-lang/crates.io-index" 3608 + checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" 3609 dependencies = [ 3610 + "windows-sys 0.52.0", 3611 ] 3612 3613 [[package]] ··· 3810 ] 3811 3812 [[package]] 3813 name = "p256" 3814 version = "0.13.2" 3815 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3922 checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" 3923 dependencies = [ 3924 "memchr", 3925 + "thiserror 2.0.16", 3926 "ucd-trie", 3927 ] 3928 ··· 4019 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 4020 4021 [[package]] 4022 + name = "pocket" 4023 + version = "0.1.0" 4024 + dependencies = [ 4025 + "atrium-crypto", 4026 + "clap", 4027 + "jwt-compact", 4028 + "log", 4029 + "poem", 4030 + "poem-openapi", 4031 + "reqwest", 4032 + "rusqlite", 4033 + "serde", 4034 + "serde_json", 4035 + "thiserror 2.0.16", 4036 + "tokio", 4037 + "tracing-subscriber", 4038 + ] 4039 + 4040 + [[package]] 4041 name = "poem" 4042 version = "3.1.12" 4043 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4075 "smallvec", 4076 "sync_wrapper", 4077 "tempfile", 4078 + "thiserror 2.0.16", 4079 "tokio", 4080 "tokio-rustls 0.26.2", 4081 "tokio-stream", ··· 4119 "serde_json", 4120 "serde_urlencoded", 4121 "serde_yaml", 4122 + "thiserror 2.0.16", 4123 "tokio", 4124 ] 4125 ··· 4138 "quote", 4139 "regex", 4140 "syn 2.0.103", 4141 + "thiserror 2.0.16", 4142 ] 4143 4144 [[package]] ··· 4287 "rustc-hash 2.1.1", 4288 "rustls 0.23.31", 4289 "socket2 0.5.9", 4290 + "thiserror 2.0.16", 4291 "tokio", 4292 "tracing", 4293 "web-time", ··· 4308 "rustls 0.23.31", 4309 "rustls-pki-types", 4310 "slab", 4311 + "thiserror 2.0.16", 4312 "tinyvec", 4313 "tracing", 4314 "web-time", ··· 4493 ] 4494 4495 [[package]] 4496 + name = "reflector" 4497 + version = "0.1.0" 4498 + dependencies = [ 4499 + "clap", 4500 + "log", 4501 + "poem", 4502 + "serde", 4503 + "tokio", 4504 + "tracing-subscriber", 4505 + ] 4506 + 4507 + [[package]] 4508 name = "regex" 4509 version = "1.11.1" 4510 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4512 dependencies = [ 4513 "aho-corasick", 4514 "memchr", 4515 + "regex-automata", 4516 + "regex-syntax", 4517 ] 4518 4519 [[package]] ··· 4524 dependencies = [ 4525 "aho-corasick", 4526 "memchr", 4527 + "regex-syntax", 4528 ] 4529 4530 [[package]] 4531 name = "regex-syntax" 4532 version = "0.8.5" 4533 source = "registry+https://github.com/rust-lang/crates.io-index" 4534 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" ··· 4656 "spki", 4657 "subtle", 4658 "zeroize", 4659 + ] 4660 + 4661 + [[package]] 4662 + name = "rusqlite" 4663 + version = "0.37.0" 4664 + source = "registry+https://github.com/rust-lang/crates.io-index" 4665 + checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" 4666 + dependencies = [ 4667 + "bitflags", 4668 + "fallible-iterator", 4669 + "fallible-streaming-iterator", 4670 + "hashlink", 4671 + "libsqlite3-sys", 4672 + "smallvec", 4673 ] 4674 4675 [[package]] ··· 4901 ] 4902 4903 [[package]] 4904 + name = "secp256k1" 4905 + version = "0.30.0" 4906 + source = "registry+https://github.com/rust-lang/crates.io-index" 4907 + checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" 4908 + dependencies = [ 4909 + "bitcoin_hashes", 4910 + "rand 0.8.5", 4911 + "secp256k1-sys", 4912 + ] 4913 + 4914 + [[package]] 4915 + name = "secp256k1-sys" 4916 + version = "0.10.1" 4917 + source = "registry+https://github.com/rust-lang/crates.io-index" 4918 + checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" 4919 + dependencies = [ 4920 + "cc", 4921 + ] 4922 + 4923 + [[package]] 4924 name = "security-framework" 4925 version = "2.11.1" 4926 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 5053 "percent-encoding", 5054 "ryu", 5055 "serde", 5056 + "thiserror 2.0.16", 5057 ] 5058 5059 [[package]] ··· 5196 dependencies = [ 5197 "num-bigint", 5198 "num-traits", 5199 + "thiserror 2.0.16", 5200 "time", 5201 ] 5202 ··· 5238 "rustls 0.23.31", 5239 "serde", 5240 "serde_json", 5241 + "thiserror 2.0.16", 5242 "time", 5243 "tokio", 5244 "tokio-util", ··· 5349 "serde", 5350 "serde_json", 5351 "serde_qs", 5352 + "thiserror 2.0.16", 5353 "tinyjson", 5354 "tokio", 5355 "tokio-tungstenite 0.27.0", ··· 5515 5516 [[package]] 5517 name = "thiserror" 5518 + version = "2.0.16" 5519 source = "registry+https://github.com/rust-lang/crates.io-index" 5520 + checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" 5521 dependencies = [ 5522 + "thiserror-impl 2.0.16", 5523 ] 5524 5525 [[package]] ··· 5535 5536 [[package]] 5537 name = "thiserror-impl" 5538 + version = "2.0.16" 5539 source = "registry+https://github.com/rust-lang/crates.io-index" 5540 + checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" 5541 dependencies = [ 5542 "proc-macro2", 5543 "quote", ··· 5640 5641 [[package]] 5642 name = "tokio" 5643 + version = "1.47.1" 5644 source = "registry+https://github.com/rust-lang/crates.io-index" 5645 + checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" 5646 dependencies = [ 5647 "backtrace", 5648 "bytes", ··· 5883 5884 [[package]] 5885 name = "tracing-subscriber" 5886 + version = "0.3.20" 5887 source = "registry+https://github.com/rust-lang/crates.io-index" 5888 + checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" 5889 dependencies = [ 5890 "matchers", 5891 "nu-ansi-term", 5892 "once_cell", 5893 + "regex-automata", 5894 "sharded-slab", 5895 "smallvec", 5896 "thread_local", ··· 5930 "native-tls", 5931 "rand 0.9.1", 5932 "sha1", 5933 + "thiserror 2.0.16", 5934 "url", 5935 "utf-8", 5936 ] ··· 5948 "log", 5949 "rand 0.9.1", 5950 "sha1", 5951 + "thiserror 2.0.16", 5952 "utf-8", 5953 ] 5954 ··· 6001 "serde_qs", 6002 "sha2", 6003 "tempfile", 6004 + "thiserror 2.0.16", 6005 "tikv-jemallocator", 6006 "tokio", 6007 "tokio-util", ··· 6335 "reqwest", 6336 "serde", 6337 "serde_json", 6338 + "thiserror 2.0.16", 6339 "tokio", 6340 "tokio-util", 6341 "url", ··· 6728 "nom", 6729 "oid-registry", 6730 "rusticata-macros", 6731 + "thiserror 2.0.16", 6732 "time", 6733 ] 6734 ··· 6839 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 6840 dependencies = [ 6841 "serde", 6842 + "zeroize_derive", 6843 + ] 6844 + 6845 + [[package]] 6846 + name = "zeroize_derive" 6847 + version = "1.4.2" 6848 + source = "registry+https://github.com/rust-lang/crates.io-index" 6849 + checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 6850 + dependencies = [ 6851 + "proc-macro2", 6852 + "quote", 6853 + "syn 2.0.103", 6854 ] 6855 6856 [[package]]
+2
Cargo.toml
··· 10 "who-am-i", 11 "slingshot", 12 "quasar", 13 ]
··· 10 "who-am-i", 11 "slingshot", 12 "quasar", 13 + "pocket", 14 + "reflector", 15 ]
+8 -1
Makefile
··· 5 cargo test --all-features 6 7 fmt: 8 - cargo fmt --package links --package constellation --package ufos --package spacedust --package who-am-i --package slingshot 9 cargo +nightly fmt --package jetstream 10 11 clippy:
··· 5 cargo test --all-features 6 7 fmt: 8 + cargo fmt --package links \ 9 + --package constellation \ 10 + --package ufos \ 11 + --package spacedust \ 12 + --package who-am-i \ 13 + --package slingshot \ 14 + --package pocket \ 15 + --package reflector 16 cargo +nightly fmt --package jetstream 17 18 clippy:
+19
pocket/Cargo.toml
···
··· 1 + [package] 2 + name = "pocket" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + atrium-crypto = "0.1.2" 8 + clap = { version = "4.5.41", features = ["derive"] } 9 + jwt-compact = { git = "https://github.com/fatfingers23/jwt-compact.git", features = ["es256k"] } 10 + log = "0.4.27" 11 + poem = { version = "3.1.12", features = ["acme", "static-files"] } 12 + poem-openapi = { version = "5.1.16", features = ["scalar"] } 13 + reqwest = { version = "0.12.22", features = ["json"] } 14 + rusqlite = "0.37.0" 15 + serde = { version = "1.0.219", features = ["derive"] } 16 + serde_json = { version = "1.0.141" } 17 + thiserror = "2.0.16" 18 + tokio = { version = "1.47.0", features = ["full"] } 19 + tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
+17
pocket/api-description.md
···
··· 1 + _A pocket dimension to stash a bit of non-public user data._ 2 + 3 + 4 + # Pocket: user preference storage 5 + 6 + This API leverages atproto service proxying to offer a bit of per-user per-app non-public data storage. 7 + Perfect for things like application preferences that might be better left out of the public PDS data. 8 + 9 + The intent is to use oauth scopes to isolate storage on a per-application basis, and to allow easy data migration from a community hosted instance to your own if you end up needing that. 10 + 11 + 12 + ### Current status 13 + 14 + > [!important] 15 + > Pocket is currently in a **v0, pre-release state**. There is one production instance and you can use it! Expect short downtimes for restarts as development progresses and occaisional data loss until it's stable. 16 + 17 + ATProto might end up adding a similar feature to [PDSs](https://atproto.com/guides/glossary#pds-personal-data-server). If/when that happens, you should use it instead of this!
+5
pocket/src/lib.rs
···
··· 1 + mod server; 2 + mod token; 3 + 4 + pub use server::serve; 5 + pub use token::TokenVerifier;
+8
pocket/src/main.rs
···
··· 1 + use pocket::serve; 2 + 3 + #[tokio::main] 4 + async fn main() { 5 + tracing_subscriber::fmt::init(); 6 + println!("Hello, world!"); 7 + serve("mac.cinnebar-tet.ts.net").await 8 + }
+202
pocket/src/server.rs
···
··· 1 + use crate::TokenVerifier; 2 + use poem::{ 3 + Endpoint, EndpointExt, Route, Server, 4 + endpoint::{StaticFileEndpoint, make_sync}, 5 + http::Method, 6 + listener::TcpListener, 7 + middleware::{CatchPanic, Cors, Tracing}, 8 + }; 9 + use poem_openapi::{ 10 + ApiResponse, ContactObject, ExternalDocumentObject, Object, OpenApi, OpenApiService, 11 + SecurityScheme, Tags, 12 + auth::Bearer, 13 + payload::{Json, PlainText}, 14 + types::Example, 15 + }; 16 + use serde::Serialize; 17 + use serde_json::{Value, json}; 18 + 19 + #[derive(Debug, SecurityScheme)] 20 + #[oai(ty = "bearer")] 21 + struct XrpcAuth(Bearer); 22 + 23 + #[derive(Tags)] 24 + enum ApiTags { 25 + /// Custom pocket APIs 26 + #[oai(rename = "Pocket APIs")] 27 + Pocket, 28 + } 29 + 30 + #[derive(Object)] 31 + #[oai(example = true)] 32 + struct XrpcErrorResponseObject { 33 + /// Should correspond an error `name` in the lexicon errors array 34 + error: String, 35 + /// Human-readable description and possibly additonal context 36 + message: String, 37 + } 38 + impl Example for XrpcErrorResponseObject { 39 + fn example() -> Self { 40 + Self { 41 + error: "PreferencesNotFound".to_string(), 42 + message: "No preferences were found for this user".to_string(), 43 + } 44 + } 45 + } 46 + type XrpcError = Json<XrpcErrorResponseObject>; 47 + fn xrpc_error(error: impl AsRef<str>, message: impl AsRef<str>) -> XrpcError { 48 + Json(XrpcErrorResponseObject { 49 + error: error.as_ref().to_string(), 50 + message: message.as_ref().to_string(), 51 + }) 52 + } 53 + 54 + #[derive(Object)] 55 + #[oai(example = true)] 56 + struct GetBskyPrefsResponseObject { 57 + /// at-uri for this record 58 + preferences: Value, 59 + } 60 + impl Example for GetBskyPrefsResponseObject { 61 + fn example() -> Self { 62 + Self { 63 + preferences: json!({ 64 + "hello": "world", 65 + }), 66 + } 67 + } 68 + } 69 + 70 + #[derive(ApiResponse)] 71 + enum GetBskyPrefsResponse { 72 + /// Record found 73 + #[oai(status = 200)] 74 + Ok(Json<GetBskyPrefsResponseObject>), 75 + /// Bad request or no preferences to return 76 + #[oai(status = 400)] 77 + BadRequest(XrpcError), 78 + } 79 + 80 + #[derive(ApiResponse)] 81 + enum PutBskyPrefsResponse { 82 + /// Record found 83 + #[oai(status = 200)] 84 + Ok(PlainText<String>), 85 + /// Bad request or no preferences to return 86 + #[oai(status = 400)] 87 + BadRequest(XrpcError), 88 + // /// Server errors 89 + // #[oai(status = 500)] 90 + // ServerError(XrpcError), 91 + } 92 + 93 + struct Xrpc { 94 + verifier: TokenVerifier, 95 + } 96 + 97 + #[OpenApi] 98 + impl Xrpc { 99 + /// com.bad-example.pocket.getPreferences 100 + /// 101 + /// get stored bluesky prefs 102 + #[oai( 103 + path = "/com.bad-example.pocket.getPreferences", 104 + method = "get", 105 + tag = "ApiTags::Pocket" 106 + )] 107 + async fn app_bsky_get_prefs(&self, XrpcAuth(auth): XrpcAuth) -> GetBskyPrefsResponse { 108 + let did = match self 109 + .verifier 110 + .verify("app.bsky.actor.getPreferences", &auth.token) 111 + .await 112 + { 113 + Ok(d) => d, 114 + Err(e) => return GetBskyPrefsResponse::BadRequest(xrpc_error("boooo", e.to_string())), 115 + }; 116 + log::info!("verified did: {did}"); 117 + // TODO: fetch from storage 118 + GetBskyPrefsResponse::Ok(Json(GetBskyPrefsResponseObject::example())) 119 + } 120 + 121 + /// com.bad-example.pocket.putPreferences 122 + /// 123 + /// store bluesky prefs 124 + #[oai( 125 + path = "/com.bad-example.pocket.putPreferences", 126 + method = "post", 127 + tag = "ApiTags::Pocket" 128 + )] 129 + async fn app_bsky_put_prefs( 130 + &self, 131 + XrpcAuth(auth): XrpcAuth, 132 + Json(prefs): Json<Value>, 133 + ) -> PutBskyPrefsResponse { 134 + let did = match self 135 + .verifier 136 + .verify("app.bsky.actor.getPreferences", &auth.token) 137 + .await 138 + { 139 + Ok(d) => d, 140 + Err(e) => return PutBskyPrefsResponse::BadRequest(xrpc_error("boooo", e.to_string())), 141 + }; 142 + log::info!("verified did: {did}"); 143 + log::warn!("received prefs: {prefs:?}"); 144 + // TODO: put prefs into storage 145 + PutBskyPrefsResponse::Ok(PlainText("hiiiiii".to_string())) 146 + } 147 + } 148 + 149 + #[derive(Debug, Clone, Serialize)] 150 + #[serde(rename_all = "camelCase")] 151 + struct AppViewService { 152 + id: String, 153 + r#type: String, 154 + service_endpoint: String, 155 + } 156 + #[derive(Debug, Clone, Serialize)] 157 + struct AppViewDoc { 158 + id: String, 159 + service: [AppViewService; 1], 160 + } 161 + /// Serve a did document for did:web for this to be an xrpc appview 162 + fn get_did_doc(domain: &str) -> impl Endpoint + use<> { 163 + let doc = poem::web::Json(AppViewDoc { 164 + id: format!("did:web:{domain}"), 165 + service: [AppViewService { 166 + id: "#pocket_prefs".to_string(), 167 + r#type: "PocketPreferences".to_string(), 168 + service_endpoint: format!("https://{domain}"), 169 + }], 170 + }); 171 + make_sync(move |_| doc.clone()) 172 + } 173 + 174 + pub async fn serve(domain: &str) -> () { 175 + let verifier = TokenVerifier::new(domain); 176 + let api_service = OpenApiService::new(Xrpc { verifier }, "Pocket", env!("CARGO_PKG_VERSION")) 177 + .server(domain) 178 + .url_prefix("/xrpc") 179 + .contact( 180 + ContactObject::new() 181 + .name("@microcosm.blue") 182 + .url("https://bsky.app/profile/microcosm.blue"), 183 + ) 184 + .description(include_str!("../api-description.md")) 185 + .external_document(ExternalDocumentObject::new("https://microcosm.blue/pocket")); 186 + 187 + let app = Route::new() 188 + .nest("/openapi", api_service.spec_endpoint()) 189 + .nest("/xrpc/", api_service) 190 + .at("/.well-known/did.json", get_did_doc(domain)) 191 + .at("/", StaticFileEndpoint::new("./static/index.html")) 192 + .with( 193 + Cors::new() 194 + .allow_method(Method::GET) 195 + .allow_method(Method::POST), 196 + ) 197 + .with(CatchPanic::new()) 198 + .with(Tracing); 199 + 200 + let listener = TcpListener::bind("127.0.0.1:3000"); 201 + Server::new(listener).name("pocket").run(app).await.unwrap(); 202 + }
+133
pocket/src/token.rs
···
··· 1 + use atrium_crypto::did::parse_multikey; 2 + use atrium_crypto::verify::Verifier; 3 + use jwt_compact::UntrustedToken; 4 + use serde::Deserialize; 5 + use std::collections::HashMap; 6 + use std::time::Duration; 7 + use thiserror::Error; 8 + 9 + #[derive(Debug, Deserialize)] 10 + struct MiniDoc { 11 + signing_key: String, 12 + did: String, 13 + } 14 + 15 + #[derive(Error, Debug)] 16 + pub enum VerifyError { 17 + #[error("The cross-service authorization token failed verification: {0}")] 18 + VerificationFailed(&'static str), 19 + #[error("Error trying to resolve the DID to a signing key, retry in a moment: {0}")] 20 + ResolutionFailed(&'static str), 21 + } 22 + 23 + pub struct TokenVerifier { 24 + domain: String, 25 + client: reqwest::Client, 26 + } 27 + 28 + impl TokenVerifier { 29 + pub fn new(domain: &str) -> Self { 30 + let client = reqwest::Client::builder() 31 + .user_agent(format!( 32 + "microcosm pocket v{} (dev: @bad-example.com)", 33 + env!("CARGO_PKG_VERSION") 34 + )) 35 + .no_proxy() 36 + .timeout(Duration::from_secs(12)) // slingshot timeout is 10s 37 + .build() 38 + .unwrap(); 39 + Self { 40 + client, 41 + domain: domain.to_string(), 42 + } 43 + } 44 + 45 + pub async fn verify(&self, expected_lxm: &str, token: &str) -> Result<String, VerifyError> { 46 + let untrusted = UntrustedToken::new(token).unwrap(); 47 + 48 + // danger! unfortunately we need to decode the DID from the jwt body before we have a public key to verify the jwt with 49 + let Ok(untrusted_claims) = 50 + untrusted.deserialize_claims_unchecked::<HashMap<String, String>>() 51 + else { 52 + return Err(VerifyError::VerificationFailed( 53 + "could not deserialize jtw claims", 54 + )); 55 + }; 56 + 57 + // get the (untrusted!) claimed DID 58 + let Some(untrusted_did) = untrusted_claims.custom.get("iss") else { 59 + return Err(VerifyError::VerificationFailed( 60 + "jwt must include the user's did in `iss`", 61 + )); 62 + }; 63 + 64 + // bail if it's not even a user-ish did 65 + if !untrusted_did.starts_with("did:") { 66 + return Err(VerifyError::VerificationFailed("iss should be a did")); 67 + } 68 + if untrusted_did.contains("#") { 69 + return Err(VerifyError::VerificationFailed( 70 + "iss should be a user did without a service identifier", 71 + )); 72 + } 73 + 74 + let endpoint = 75 + "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc"; 76 + let doc: MiniDoc = self 77 + .client 78 + .get(format!("{endpoint}?identifier={untrusted_did}")) 79 + .send() 80 + .await 81 + .map_err(|_| VerifyError::ResolutionFailed("failed to fetch minidoc"))? 82 + .error_for_status() 83 + .map_err(|_| VerifyError::ResolutionFailed("non-ok response for minidoc"))? 84 + .json() 85 + .await 86 + .map_err(|_| VerifyError::ResolutionFailed("failed to parse json to minidoc"))?; 87 + 88 + // sanity check before we go ahead with this signing key 89 + if doc.did != *untrusted_did { 90 + return Err(VerifyError::VerificationFailed( 91 + "wtf, resolveMiniDoc returned a doc for a different DID, slingshot bug", 92 + )); 93 + } 94 + 95 + let Ok((alg, public_key)) = parse_multikey(&doc.signing_key) else { 96 + return Err(VerifyError::VerificationFailed( 97 + "could not parse signing key form minidoc", 98 + )); 99 + }; 100 + 101 + // i _guess_ we've successfully bootstrapped the verification of the jwt unless this fails 102 + if let Err(e) = Verifier::default().verify( 103 + alg, 104 + &public_key, 105 + &untrusted.signed_data, 106 + untrusted.signature_bytes(), 107 + ) { 108 + log::warn!("jwt verification failed: {e}"); 109 + return Err(VerifyError::VerificationFailed( 110 + "jwt signature verification failed", 111 + )); 112 + } 113 + 114 + // past this point we're should have established trust. crossing ts and dotting is. 115 + let did = &untrusted_did; 116 + let claims = &untrusted_claims; 117 + 118 + let Some(aud) = claims.custom.get("aud") else { 119 + return Err(VerifyError::VerificationFailed("missing aud")); 120 + }; 121 + if *aud != format!("did:web:{}#bsky_appview", self.domain) { 122 + return Err(VerifyError::VerificationFailed("wrong aud")); 123 + } 124 + let Some(lxm) = claims.custom.get("lxm") else { 125 + return Err(VerifyError::VerificationFailed("missing lxm")); 126 + }; 127 + if lxm != expected_lxm { 128 + return Err(VerifyError::VerificationFailed("wrong lxm")); 129 + } 130 + 131 + Ok(did.to_string()) 132 + } 133 + }
+67
pocket/static/index.html
···
··· 1 + <!doctype html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="utf-8" /> 5 + <title>Pocket: atproto user preference storage</title> 6 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 7 + <meta name="description" content="API Documentation for Pocket, a simple user-preference storage system for atproto" /> 8 + <style> 9 + :root { 10 + --scalar-small: 13px; 11 + } 12 + .scalar-app .markdown .markdown-alert { 13 + font-size: var(--scalar-small); 14 + } 15 + .sidebar-heading-link-title { 16 + line-height: 1.2; 17 + } 18 + .custom-header { 19 + height: 42px; 20 + background-color: #221828; 21 + box-shadow: inset 0 -1px 0 var(--scalar-border-color); 22 + color: var(--scalar-color-1); 23 + font-size: var(--scalar-font-size-3); 24 + font-family: 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif; 25 + padding: 0 18px; 26 + justify-content: space-between; 27 + } 28 + .custom-header, 29 + .custom-header nav { 30 + display: flex; 31 + align-items: center; 32 + gap: 18px; 33 + } 34 + .custom-header a:hover { 35 + color: var(--scalar-color-2); 36 + } 37 + 38 + .light-mode .custom-header { 39 + background-color: thistle; 40 + } 41 + </style> 42 + </head> 43 + <body> 44 + <header class="custom-header scalar-app"> 45 + <p> 46 + TODO: thing 47 + </p> 48 + <nav> 49 + <b>a <a href="https://microcosm.blue">microcosm</a> project</b> 50 + <a href="https://bsky.app/profile/microcosm.blue">@microcosm.blue</a> 51 + <a href="https://github.com/at-microcosm">github</a> 52 + </nav> 53 + </header> 54 + 55 + <script id="api-reference" type="application/json" data-url="/openapi"></script> 56 + 57 + <script> 58 + var configuration = { 59 + theme: 'purple', 60 + hideModels: true, 61 + } 62 + document.getElementById('api-reference').dataset.configuration = JSON.stringify(configuration) 63 + </script> 64 + 65 + <script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"></script> 66 + </body> 67 + </html>
+2
quasar/src/lib.rs
··· 1 mod storage;
··· 1 mod storage; 2 + 3 + pub use storage::Storage;
+4
quasar/src/storage.rs
···
··· 1 + 2 + pub trait Storage { 3 + 4 + }
+12
reflector/Cargo.toml
···
··· 1 + [package] 2 + name = "reflector" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + clap = { version = "4.5.47", features = ["derive"] } 8 + log = "0.4.28" 9 + poem = "3.1.12" 10 + serde = { version = "1.0.219", features = ["derive"] } 11 + tokio = "1.47.1" 12 + tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
+9
reflector/readme.md
···
··· 1 + # reflector 2 + 3 + a tiny did:web service server that maps subdomains to a single service endpoint 4 + 5 + receiving requests from multiple subdomains is left as a problem for the reverse proxy to solve, since acme wildcard certificates (ie. letsencrypt) require the most complicated and involved challenge type (DNS). 6 + 7 + caddy [has good support for](https://caddyserver.com/docs/caddyfile/patterns#wildcard-certificates) configuring the wildcard DNS challenge with various DNS providers, and also supports [on-demand](https://caddyserver.com/docs/automatic-https#using-on-demand-tls) provisioning via the simpler methods. 8 + 9 + if you only need a small fixed number of subdomains, you can also use certbot or otherwise individually configure them in your reverse proxy.
+83
reflector/src/main.rs
···
··· 1 + use clap::Parser; 2 + use poem::{ 3 + EndpointExt, Route, Server, get, handler, 4 + listener::TcpListener, 5 + middleware::{AddData, Tracing}, 6 + web::{Data, Json, TypedHeader, headers::Host}, 7 + }; 8 + use serde::Serialize; 9 + 10 + #[handler] 11 + fn hello() -> String { 12 + "ɹoʇɔǝʅⅎǝɹ".to_string() 13 + } 14 + 15 + #[derive(Debug, Serialize)] 16 + struct DidDoc { 17 + id: String, 18 + service: [DidService; 1], 19 + } 20 + 21 + #[derive(Debug, Clone, Serialize)] 22 + struct DidService { 23 + id: String, 24 + r#type: String, 25 + service_endpoint: String, 26 + } 27 + 28 + #[handler] 29 + fn did_doc(TypedHeader(host): TypedHeader<Host>, service: Data<&DidService>) -> Json<DidDoc> { 30 + Json(DidDoc { 31 + id: format!("did:web:{}", host.hostname()), 32 + service: [service.clone()], 33 + }) 34 + } 35 + 36 + /// Slingshot record edge cache 37 + #[derive(Parser, Debug, Clone)] 38 + #[command(version, about, long_about = None)] 39 + struct Args { 40 + /// The DID document service ID to serve 41 + /// 42 + /// must start with a '#', like `#bsky_appview' 43 + #[arg(long)] 44 + id: String, 45 + /// Service type 46 + /// 47 + /// Not sure exactly what its requirements are. 'BlueskyAppview' for example 48 + #[arg(long)] 49 + r#type: String, 50 + /// The HTTPS endpoint for the service 51 + #[arg(long)] 52 + service_endpoint: String, 53 + } 54 + 55 + impl From<Args> for DidService { 56 + fn from(a: Args) -> Self { 57 + Self { 58 + id: a.id, 59 + r#type: a.r#type, 60 + service_endpoint: a.service_endpoint, 61 + } 62 + } 63 + } 64 + 65 + #[tokio::main(flavor = "current_thread")] 66 + async fn main() { 67 + tracing_subscriber::fmt::init(); 68 + log::info!("ɹoʇɔǝʅⅎǝɹ"); 69 + 70 + let args = Args::parse(); 71 + let service: DidService = args.into(); 72 + 73 + Server::new(TcpListener::bind("0.0.0.0:3001")) 74 + .run( 75 + Route::new() 76 + .at("/", get(hello)) 77 + .at("/.well-known/did.json", get(did_doc)) 78 + .with(AddData::new(service)) 79 + .with(Tracing), 80 + ) 81 + .await 82 + .unwrap() 83 + }