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

basic token verification works

Changed files
+561 -33
pocket
quasar
+243 -33
Cargo.lock
··· 192 192 "nom", 193 193 "num-traits", 194 194 "rusticata-macros", 195 - "thiserror 2.0.12", 195 + "thiserror 2.0.16", 196 196 "time", 197 197 ] 198 198 ··· 371 371 ] 372 372 373 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]] 374 387 name = "atrium-identity" 375 388 version = "0.1.5" 376 389 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 628 641 "axum", 629 642 "handlebars", 630 643 "serde", 631 - "thiserror 2.0.12", 644 + "thiserror 2.0.16", 632 645 ] 633 646 634 647 [[package]] ··· 774 787 ] 775 788 776 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]] 777 806 name = "bitflags" 778 807 version = "2.9.0" 779 808 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 907 936 ] 908 937 909 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]] 910 966 name = "cid" 911 967 version = "0.11.1" 912 968 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1172 1228 checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 1173 1229 1174 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]] 1175 1237 name = "crypto-bigint" 1176 1238 version = "0.5.5" 1177 1239 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1514 1576 "slog-bunyan", 1515 1577 "slog-json", 1516 1578 "slog-term", 1517 - "thiserror 2.0.12", 1579 + "thiserror 2.0.16", 1518 1580 "tokio", 1519 1581 "tokio-rustls 0.25.0", 1520 1582 "toml", ··· 1684 1746 ] 1685 1747 1686 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]] 1687 1761 name = "fastrand" 1688 1762 version = "2.3.0" 1689 1763 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1814 1888 "mixtrics", 1815 1889 "pin-project", 1816 1890 "serde", 1817 - "thiserror 2.0.12", 1891 + "thiserror 2.0.16", 1818 1892 "tokio", 1819 1893 "tracing", 1820 1894 ] ··· 1834 1908 "parking_lot", 1835 1909 "pin-project", 1836 1910 "serde", 1837 - "thiserror 2.0.12", 1911 + "thiserror 2.0.16", 1838 1912 "tokio", 1839 1913 "twox-hash", 1840 1914 ] ··· 1867 1941 "parking_lot", 1868 1942 "pin-project", 1869 1943 "serde", 1870 - "thiserror 2.0.12", 1944 + "thiserror 2.0.16", 1871 1945 "tokio", 1872 1946 "tracing", 1873 1947 ] ··· 1899 1973 "pin-project", 1900 1974 "rand 0.9.1", 1901 1975 "serde", 1902 - "thiserror 2.0.12", 1976 + "thiserror 2.0.16", 1903 1977 "tokio", 1904 1978 "tracing", 1905 1979 "twox-hash", ··· 2121 2195 ] 2122 2196 2123 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]] 2124 2208 name = "handlebars" 2125 2209 version = "6.3.2" 2126 2210 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2133 2217 "pest_derive", 2134 2218 "serde", 2135 2219 "serde_json", 2136 - "thiserror 2.0.12", 2220 + "thiserror 2.0.16", 2137 2221 "walkdir", 2138 2222 ] 2139 2223 ··· 2170 2254 ] 2171 2255 2172 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]] 2173 2266 name = "headers" 2174 2267 version = "0.4.0" 2175 2268 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2223 2316 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 2224 2317 2225 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]] 2226 2328 name = "hickory-proto" 2227 2329 version = "0.25.2" 2228 2330 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2240 2342 "once_cell", 2241 2343 "rand 0.9.1", 2242 2344 "ring", 2243 - "thiserror 2.0.12", 2345 + "thiserror 2.0.16", 2244 2346 "tinyvec", 2245 2347 "tokio", 2246 2348 "tracing", ··· 2263 2365 "rand 0.9.1", 2264 2366 "resolv-conf", 2265 2367 "smallvec", 2266 - "thiserror 2.0.12", 2368 + "thiserror 2.0.16", 2267 2369 "tokio", 2268 2370 "tracing", 2269 2371 ] ··· 2757 2859 "metrics", 2758 2860 "serde", 2759 2861 "serde_json", 2760 - "thiserror 2.0.12", 2862 + "thiserror 2.0.16", 2761 2863 "tokio", 2762 2864 "tokio-tungstenite 0.26.2", 2763 2865 "url", ··· 2860 2962 ] 2861 2963 2862 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]] 2863 2998 name = "langtag" 2864 2999 version = "0.3.4" 2865 3000 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2952 3087 ] 2953 3088 2954 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]] 2955 3100 name = "libz-sys" 2956 3101 version = "1.1.22" 2957 3102 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2969 3114 "anyhow", 2970 3115 "fluent-uri", 2971 3116 "nom", 2972 - "thiserror 2.0.12", 3117 + "thiserror 2.0.16", 2973 3118 "tinyjson", 2974 3119 ] 2975 3120 ··· 3235 3380 "metrics", 3236 3381 "metrics-util 0.20.0", 3237 3382 "quanta", 3238 - "thiserror 2.0.12", 3383 + "thiserror 2.0.16", 3239 3384 "tokio", 3240 3385 "tracing", 3241 3386 ] ··· 3784 3929 checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" 3785 3930 dependencies = [ 3786 3931 "memchr", 3787 - "thiserror 2.0.12", 3932 + "thiserror 2.0.16", 3788 3933 "ucd-trie", 3789 3934 ] 3790 3935 ··· 3881 4026 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 3882 4027 3883 4028 [[package]] 4029 + name = "pocket" 4030 + version = "0.1.0" 4031 + dependencies = [ 4032 + "atrium-crypto", 4033 + "clap", 4034 + "jwt-compact", 4035 + "log", 4036 + "poem", 4037 + "poem-openapi", 4038 + "reqwest", 4039 + "rusqlite", 4040 + "serde", 4041 + "serde_json", 4042 + "thiserror 2.0.16", 4043 + "tokio", 4044 + "tracing-subscriber", 4045 + ] 4046 + 4047 + [[package]] 3884 4048 name = "poem" 3885 4049 version = "3.1.12" 3886 4050 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3918 4082 "smallvec", 3919 4083 "sync_wrapper", 3920 4084 "tempfile", 3921 - "thiserror 2.0.12", 4085 + "thiserror 2.0.16", 3922 4086 "tokio", 3923 4087 "tokio-rustls 0.26.2", 3924 4088 "tokio-stream", ··· 3962 4126 "serde_json", 3963 4127 "serde_urlencoded", 3964 4128 "serde_yaml", 3965 - "thiserror 2.0.12", 4129 + "thiserror 2.0.16", 3966 4130 "tokio", 3967 4131 ] 3968 4132 ··· 3981 4145 "quote", 3982 4146 "regex", 3983 4147 "syn 2.0.103", 3984 - "thiserror 2.0.12", 4148 + "thiserror 2.0.16", 3985 4149 ] 3986 4150 3987 4151 [[package]] ··· 4130 4294 "rustc-hash 2.1.1", 4131 4295 "rustls 0.23.31", 4132 4296 "socket2 0.5.9", 4133 - "thiserror 2.0.12", 4297 + "thiserror 2.0.16", 4134 4298 "tokio", 4135 4299 "tracing", 4136 4300 "web-time", ··· 4151 4315 "rustls 0.23.31", 4152 4316 "rustls-pki-types", 4153 4317 "slab", 4154 - "thiserror 2.0.12", 4318 + "thiserror 2.0.16", 4155 4319 "tinyvec", 4156 4320 "tracing", 4157 4321 "web-time", ··· 4505 4669 ] 4506 4670 4507 4671 [[package]] 4672 + name = "rusqlite" 4673 + version = "0.37.0" 4674 + source = "registry+https://github.com/rust-lang/crates.io-index" 4675 + checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" 4676 + dependencies = [ 4677 + "bitflags", 4678 + "fallible-iterator", 4679 + "fallible-streaming-iterator", 4680 + "hashlink", 4681 + "libsqlite3-sys", 4682 + "smallvec", 4683 + ] 4684 + 4685 + [[package]] 4508 4686 name = "rustc-demangle" 4509 4687 version = "0.1.24" 4510 4688 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4733 4911 ] 4734 4912 4735 4913 [[package]] 4914 + name = "secp256k1" 4915 + version = "0.30.0" 4916 + source = "registry+https://github.com/rust-lang/crates.io-index" 4917 + checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" 4918 + dependencies = [ 4919 + "bitcoin_hashes", 4920 + "rand 0.8.5", 4921 + "secp256k1-sys", 4922 + ] 4923 + 4924 + [[package]] 4925 + name = "secp256k1-sys" 4926 + version = "0.10.1" 4927 + source = "registry+https://github.com/rust-lang/crates.io-index" 4928 + checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" 4929 + dependencies = [ 4930 + "cc", 4931 + ] 4932 + 4933 + [[package]] 4736 4934 name = "security-framework" 4737 4935 version = "2.11.1" 4738 4936 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4865 5063 "percent-encoding", 4866 5064 "ryu", 4867 5065 "serde", 4868 - "thiserror 2.0.12", 5066 + "thiserror 2.0.16", 4869 5067 ] 4870 5068 4871 5069 [[package]] ··· 5008 5206 dependencies = [ 5009 5207 "num-bigint", 5010 5208 "num-traits", 5011 - "thiserror 2.0.12", 5209 + "thiserror 2.0.16", 5012 5210 "time", 5013 5211 ] 5014 5212 ··· 5050 5248 "rustls 0.23.31", 5051 5249 "serde", 5052 5250 "serde_json", 5053 - "thiserror 2.0.12", 5251 + "thiserror 2.0.16", 5054 5252 "time", 5055 5253 "tokio", 5056 5254 "tokio-util", ··· 5161 5359 "serde", 5162 5360 "serde_json", 5163 5361 "serde_qs", 5164 - "thiserror 2.0.12", 5362 + "thiserror 2.0.16", 5165 5363 "tinyjson", 5166 5364 "tokio", 5167 5365 "tokio-tungstenite 0.27.0", ··· 5327 5525 5328 5526 [[package]] 5329 5527 name = "thiserror" 5330 - version = "2.0.12" 5528 + version = "2.0.16" 5331 5529 source = "registry+https://github.com/rust-lang/crates.io-index" 5332 - checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 5530 + checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" 5333 5531 dependencies = [ 5334 - "thiserror-impl 2.0.12", 5532 + "thiserror-impl 2.0.16", 5335 5533 ] 5336 5534 5337 5535 [[package]] ··· 5347 5545 5348 5546 [[package]] 5349 5547 name = "thiserror-impl" 5350 - version = "2.0.12" 5548 + version = "2.0.16" 5351 5549 source = "registry+https://github.com/rust-lang/crates.io-index" 5352 - checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 5550 + checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" 5353 5551 dependencies = [ 5354 5552 "proc-macro2", 5355 5553 "quote", ··· 5742 5940 "native-tls", 5743 5941 "rand 0.9.1", 5744 5942 "sha1", 5745 - "thiserror 2.0.12", 5943 + "thiserror 2.0.16", 5746 5944 "url", 5747 5945 "utf-8", 5748 5946 ] ··· 5760 5958 "log", 5761 5959 "rand 0.9.1", 5762 5960 "sha1", 5763 - "thiserror 2.0.12", 5961 + "thiserror 2.0.16", 5764 5962 "utf-8", 5765 5963 ] 5766 5964 ··· 5813 6011 "serde_qs", 5814 6012 "sha2", 5815 6013 "tempfile", 5816 - "thiserror 2.0.12", 6014 + "thiserror 2.0.16", 5817 6015 "tikv-jemallocator", 5818 6016 "tokio", 5819 6017 "tokio-util", ··· 6147 6345 "reqwest", 6148 6346 "serde", 6149 6347 "serde_json", 6150 - "thiserror 2.0.12", 6348 + "thiserror 2.0.16", 6151 6349 "tokio", 6152 6350 "tokio-util", 6153 6351 "url", ··· 6540 6738 "nom", 6541 6739 "oid-registry", 6542 6740 "rusticata-macros", 6543 - "thiserror 2.0.12", 6741 + "thiserror 2.0.16", 6544 6742 "time", 6545 6743 ] 6546 6744 ··· 6651 6849 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 6652 6850 dependencies = [ 6653 6851 "serde", 6852 + "zeroize_derive", 6853 + ] 6854 + 6855 + [[package]] 6856 + name = "zeroize_derive" 6857 + version = "1.4.2" 6858 + source = "registry+https://github.com/rust-lang/crates.io-index" 6859 + checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" 6860 + dependencies = [ 6861 + "proc-macro2", 6862 + "quote", 6863 + "syn 2.0.103", 6654 6864 ] 6655 6865 6656 6866 [[package]]
+1
Cargo.toml
··· 10 10 "who-am-i", 11 11 "slingshot", 12 12 "quasar", 13 + "pocket", 13 14 ]
+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"] }
+5
pocket/src/lib.rs
··· 1 + mod server; 2 + mod token; 3 + 4 + pub use server::serve; 5 + pub use token::verify;
+9
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 + } 9 +
+208
pocket/src/server.rs
··· 1 + use poem::{ 2 + endpoint::make_sync, 3 + Endpoint, 4 + Route, 5 + Server, 6 + EndpointExt, 7 + http::{Method, HeaderMap}, 8 + middleware::{CatchPanic, Cors, Tracing}, 9 + listener::TcpListener, 10 + }; 11 + use poem_openapi::{ 12 + ContactObject, 13 + ExternalDocumentObject, 14 + OpenApi, 15 + OpenApiService, 16 + Tags, 17 + Object, 18 + ApiResponse, 19 + types::Example, 20 + auth::Bearer, 21 + payload::Json, 22 + SecurityScheme, 23 + }; 24 + use crate::verify; 25 + use serde::Serialize; 26 + use serde_json::{Value, json}; 27 + 28 + 29 + #[derive(Debug, SecurityScheme)] 30 + #[oai(ty = "bearer")] 31 + struct BlahAuth(Bearer); 32 + 33 + 34 + #[derive(Tags)] 35 + enum ApiTags { 36 + /// Bluesky-compatible APIs. 37 + #[oai(rename = "app.bsky.* queries")] 38 + AppBsky, 39 + } 40 + 41 + #[derive(Object)] 42 + #[oai(example = true)] 43 + struct XrpcErrorResponseObject { 44 + /// Should correspond an error `name` in the lexicon errors array 45 + error: String, 46 + /// Human-readable description and possibly additonal context 47 + message: String, 48 + } 49 + impl Example for XrpcErrorResponseObject { 50 + fn example() -> Self { 51 + Self { 52 + error: "PreferencesNotFound".to_string(), 53 + message: "No preferences were found for this user".to_string(), 54 + } 55 + } 56 + } 57 + type XrpcError = Json<XrpcErrorResponseObject>; 58 + fn xrpc_error(error: impl AsRef<str>, message: impl AsRef<str>) -> XrpcError { 59 + Json(XrpcErrorResponseObject { 60 + error: error.as_ref().to_string(), 61 + message: message.as_ref().to_string(), 62 + }) 63 + } 64 + 65 + #[derive(Object)] 66 + #[oai(example = true)] 67 + struct GetBskyPrefsResponseObject { 68 + /// at-uri for this record 69 + preferences: Value, 70 + } 71 + impl Example for GetBskyPrefsResponseObject { 72 + fn example() -> Self { 73 + Self { 74 + preferences: json!({ 75 + "hello": "world", 76 + }), 77 + } 78 + } 79 + } 80 + 81 + #[derive(ApiResponse)] 82 + enum GetBskyPrefsResponse { 83 + /// Record found 84 + #[oai(status = 200)] 85 + Ok(Json<GetBskyPrefsResponseObject>), 86 + /// Bad request or no preferences to return 87 + #[oai(status = 400)] 88 + BadRequest(XrpcError), 89 + // /// Server errors 90 + // #[oai(status = 500)] 91 + // ServerError(XrpcError), 92 + } 93 + 94 + struct Xrpc { 95 + domain: String, 96 + } 97 + 98 + #[OpenApi] 99 + impl Xrpc { 100 + /// app.bsky.actor.getPreferences 101 + /// 102 + /// get stored bluesky prefs 103 + #[oai( 104 + path = "/app.bsky.actor.getPreferences", 105 + method = "get", 106 + tag = "ApiTags::AppBsky" 107 + )] 108 + async fn app_bsky_get_prefs( 109 + &self, 110 + BlahAuth(auth): BlahAuth, 111 + m: &HeaderMap, 112 + ) -> GetBskyPrefsResponse { 113 + log::warn!("hm: {m:?}"); 114 + match verify( 115 + &format!("did:web:{}#bsky_appview", self.domain), 116 + "app.bsky.actor.getPreferences", 117 + &auth.token, 118 + ).await { 119 + Ok(did) => log::info!("wooo! {did}"), 120 + Err(err) => return GetBskyPrefsResponse::BadRequest(xrpc_error("booo", err)), 121 + }; 122 + log::warn!("got bearer: {:?}", auth.token); 123 + GetBskyPrefsResponse::Ok(Json(GetBskyPrefsResponseObject::example())) 124 + } 125 + 126 + /// app.bsky.actor.putPreferences 127 + /// 128 + /// store bluesky prefs 129 + #[oai( 130 + path = "/app.bsky.actor.putPreferences", 131 + method = "post", 132 + tag = "ApiTags::AppBsky" 133 + )] 134 + async fn app_bsky_put_prefs( 135 + &self, 136 + Json(prefs): Json<Value>, 137 + ) -> () { 138 + log::warn!("received prefs: {prefs:?}"); 139 + () 140 + } 141 + } 142 + 143 + #[derive(Debug, Clone, Serialize)] 144 + #[serde(rename_all = "camelCase")] 145 + struct AppViewService { 146 + id: String, 147 + r#type: String, 148 + service_endpoint: String, 149 + } 150 + #[derive(Debug, Clone, Serialize)] 151 + struct AppViewDoc { 152 + id: String, 153 + service: [AppViewService; 1], 154 + } 155 + /// Serve a did document for did:web for this to be an xrpc appview 156 + fn get_did_doc(domain: &str) -> impl Endpoint + use<> { 157 + let doc = poem::web::Json(AppViewDoc { 158 + id: format!("did:web:{domain}"), 159 + service: [AppViewService { 160 + id: "#bsky_appview".to_string(), 161 + r#type: "PocketBlueskyPreferences".to_string(), 162 + service_endpoint: format!("https://{domain}"), 163 + }], 164 + }); 165 + make_sync(move |_| doc.clone()) 166 + } 167 + 168 + pub async fn serve( 169 + domain: &str, 170 + ) -> () { 171 + let api_service = OpenApiService::new( 172 + Xrpc { domain: domain.to_string() }, 173 + "Pocket", 174 + env!("CARGO_PKG_VERSION"), 175 + ) 176 + .server(domain) 177 + .url_prefix("/xrpc") 178 + .contact( 179 + ContactObject::new() 180 + .name("@microcosm.blue") 181 + .url("https://bsky.app/profile/microcosm.blue"), 182 + ) 183 + // .description(include_str!("../api-description.md")) 184 + .external_document(ExternalDocumentObject::new( 185 + "https://microcosm.blue/pocket", 186 + )); 187 + 188 + let app = Route::new() 189 + .at("/.well-known/did.json", get_did_doc(&domain)) 190 + .nest("/xrpc/", api_service) 191 + // .at("/", StaticFileEndpoint::new("./static/index.html")) 192 + // .nest("/openapi", api_service.spec_endpoint()) 193 + .with( 194 + Cors::new() 195 + .allow_method(Method::GET) 196 + .allow_method(Method::POST) 197 + ) 198 + .with(CatchPanic::new()) 199 + .with(Tracing); 200 + 201 + let listener = TcpListener::bind("127.0.0.1:3000"); 202 + Server::new(listener) 203 + .name("pocket") 204 + .run(app) 205 + .await 206 + .unwrap(); 207 + 208 + }
+72
pocket/src/token.rs
··· 1 + use jwt_compact::{Claims, UntrustedToken}; 2 + use atrium_crypto::did::parse_multikey; 3 + use atrium_crypto::verify::Verifier; 4 + use std::collections::HashMap; 5 + use serde::Deserialize; 6 + 7 + #[derive(Debug, Deserialize)] 8 + struct MiniDoc { 9 + signing_key: String, 10 + } 11 + 12 + pub async fn verify( 13 + expected_aud: &str, 14 + expected_lxm: &str, 15 + token: &str, 16 + ) -> Result<String, &'static str> { 17 + let untrusted = UntrustedToken::new(token).unwrap(); 18 + 19 + let claims: Claims<HashMap<String, String>> = untrusted.deserialize_claims_unchecked().unwrap(); 20 + 21 + let Some(did) = claims.custom.get("iss") else { 22 + return Err("jwt must include the user's did in `iss`"); 23 + }; 24 + 25 + if !did.starts_with("did:") { 26 + return Err("iss should be a did"); 27 + } 28 + if did.contains("#") { 29 + return Err("iss should be a user did without a service identifier"); 30 + } 31 + 32 + println!("Claims: {claims:#?}"); 33 + println!("did: {did:#?}"); 34 + 35 + let endpoint = "https://slingshot.microcosm.blue/xrpc/com.bad-example.identity.resolveMiniDoc"; 36 + let doc: MiniDoc = reqwest::get(format!("{endpoint}?identifier={did}")) 37 + .await 38 + .unwrap() 39 + .error_for_status() 40 + .unwrap() 41 + .json() 42 + .await 43 + .unwrap(); 44 + 45 + log::info!("got minidoc response: {doc:?}"); 46 + 47 + let (alg, public_key) = parse_multikey(&doc.signing_key).unwrap(); 48 + log::info!("parsed key: {public_key:?}"); 49 + 50 + Verifier::default().verify( 51 + alg, 52 + &public_key, 53 + &untrusted.signed_data, 54 + untrusted.signature_bytes(), 55 + ).unwrap(); 56 + // if this passes, then our claims were trustworthy after all(??) 57 + 58 + let Some(aud) = claims.custom.get("aud") else { 59 + return Err("missing aud"); 60 + }; 61 + if aud != expected_aud { 62 + return Err("wrong aud"); 63 + } 64 + let Some(lxm) = claims.custom.get("lxm") else { 65 + return Err("missing lxm"); 66 + }; 67 + if lxm != expected_lxm { 68 + return Err("wrong lxm"); 69 + } 70 + 71 + Ok(did.to_string()) 72 + }
+4
quasar/src/storage.rs
··· 1 + 2 + trait Storage { 3 + 4 + }