+1
-1
.github/workflows/checks.yml
+1
-1
.github/workflows/checks.yml
···
28
28
- name: get nightly toolchain for jetstream fmt
29
29
run: rustup toolchain install nightly --allow-downgrade -c rustfmt
30
30
- name: fmt
31
-
run: cargo fmt --package links --package constellation --package ufos --package spacedust --package who-am-i --package slingshot -- --check
31
+
run: cargo fmt --package links --package constellation --package ufos --package spacedust --package who-am-i --package slingshot --package pocket -- --check
32
32
- name: fmt jetstream (nightly)
33
33
run: cargo +nightly fmt --package jetstream -- --check
34
34
- name: clippy
+277
-77
Cargo.lock
+277
-77
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"
···
933
989
934
990
[[package]]
935
991
name = "clap"
936
-
version = "4.5.46"
992
+
version = "4.5.47"
937
993
source = "registry+https://github.com/rust-lang/crates.io-index"
938
-
checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57"
994
+
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
939
995
dependencies = [
940
996
"clap_builder",
941
997
"clap_derive",
···
943
999
944
1000
[[package]]
945
1001
name = "clap_builder"
946
-
version = "4.5.46"
1002
+
version = "4.5.47"
947
1003
source = "registry+https://github.com/rust-lang/crates.io-index"
948
-
checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41"
1004
+
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
949
1005
dependencies = [
950
1006
"anstream",
951
1007
"anstyle",
···
955
1011
956
1012
[[package]]
957
1013
name = "clap_derive"
958
-
version = "4.5.45"
1014
+
version = "4.5.47"
959
1015
source = "registry+https://github.com/rust-lang/crates.io-index"
960
-
checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6"
1016
+
checksum = "bbfd7eae0b0f1a6e63d4b13c9c478de77c2eb546fba158ad50b4203dc24b9f9c"
961
1017
dependencies = [
962
1018
"heck",
963
1019
"proc-macro2",
···
1170
1226
version = "0.8.21"
1171
1227
source = "registry+https://github.com/rust-lang/crates.io-index"
1172
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"
1173
1235
1174
1236
[[package]]
1175
1237
name = "crypto-bigint"
···
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
···
2167
2251
"allocator-api2",
2168
2252
"equivalent",
2169
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",
2170
2263
]
2171
2264
2172
2265
[[package]]
···
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
···
3003
3148
3004
3149
[[package]]
3005
3150
name = "log"
3006
-
version = "0.4.27"
3151
+
version = "0.4.28"
3007
3152
source = "registry+https://github.com/rust-lang/crates.io-index"
3008
-
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
3153
+
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
3009
3154
3010
3155
[[package]]
3011
3156
name = "loom"
···
3155
3300
3156
3301
[[package]]
3157
3302
name = "matchers"
3158
-
version = "0.1.0"
3303
+
version = "0.2.0"
3159
3304
source = "registry+https://github.com/rust-lang/crates.io-index"
3160
-
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
3305
+
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
3161
3306
dependencies = [
3162
-
"regex-automata 0.1.10",
3307
+
"regex-automata",
3163
3308
]
3164
3309
3165
3310
[[package]]
···
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
]
···
3458
3603
3459
3604
[[package]]
3460
3605
name = "nu-ansi-term"
3461
-
version = "0.46.0"
3606
+
version = "0.50.1"
3462
3607
source = "registry+https://github.com/rust-lang/crates.io-index"
3463
-
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
3608
+
checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
3464
3609
dependencies = [
3465
-
"overload",
3466
-
"winapi",
3610
+
"windows-sys 0.52.0",
3467
3611
]
3468
3612
3469
3613
[[package]]
···
3666
3810
]
3667
3811
3668
3812
[[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
3813
name = "p256"
3676
3814
version = "0.13.2"
3677
3815
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3784
3922
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
3785
3923
dependencies = [
3786
3924
"memchr",
3787
-
"thiserror 2.0.12",
3925
+
"thiserror 2.0.16",
3788
3926
"ucd-trie",
3789
3927
]
3790
3928
···
3881
4019
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
3882
4020
3883
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]]
3884
4041
name = "poem"
3885
4042
version = "3.1.12"
3886
4043
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3918
4075
"smallvec",
3919
4076
"sync_wrapper",
3920
4077
"tempfile",
3921
-
"thiserror 2.0.12",
4078
+
"thiserror 2.0.16",
3922
4079
"tokio",
3923
4080
"tokio-rustls 0.26.2",
3924
4081
"tokio-stream",
···
3962
4119
"serde_json",
3963
4120
"serde_urlencoded",
3964
4121
"serde_yaml",
3965
-
"thiserror 2.0.12",
4122
+
"thiserror 2.0.16",
3966
4123
"tokio",
3967
4124
]
3968
4125
···
3981
4138
"quote",
3982
4139
"regex",
3983
4140
"syn 2.0.103",
3984
-
"thiserror 2.0.12",
4141
+
"thiserror 2.0.16",
3985
4142
]
3986
4143
3987
4144
[[package]]
···
4130
4287
"rustc-hash 2.1.1",
4131
4288
"rustls 0.23.31",
4132
4289
"socket2 0.5.9",
4133
-
"thiserror 2.0.12",
4290
+
"thiserror 2.0.16",
4134
4291
"tokio",
4135
4292
"tracing",
4136
4293
"web-time",
···
4151
4308
"rustls 0.23.31",
4152
4309
"rustls-pki-types",
4153
4310
"slab",
4154
-
"thiserror 2.0.12",
4311
+
"thiserror 2.0.16",
4155
4312
"tinyvec",
4156
4313
"tracing",
4157
4314
"web-time",
···
4336
4493
]
4337
4494
4338
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]]
4339
4508
name = "regex"
4340
4509
version = "1.11.1"
4341
4510
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4343
4512
dependencies = [
4344
4513
"aho-corasick",
4345
4514
"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",
4515
+
"regex-automata",
4516
+
"regex-syntax",
4357
4517
]
4358
4518
4359
4519
[[package]]
···
4364
4524
dependencies = [
4365
4525
"aho-corasick",
4366
4526
"memchr",
4367
-
"regex-syntax 0.8.5",
4527
+
"regex-syntax",
4368
4528
]
4369
4529
4370
4530
[[package]]
4371
4531
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
4532
version = "0.8.5"
4379
4533
source = "registry+https://github.com/rust-lang/crates.io-index"
4380
4534
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
···
4502
4656
"spki",
4503
4657
"subtle",
4504
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",
4505
4673
]
4506
4674
4507
4675
[[package]]
···
4733
4901
]
4734
4902
4735
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]]
4736
4924
name = "security-framework"
4737
4925
version = "2.11.1"
4738
4926
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4865
5053
"percent-encoding",
4866
5054
"ryu",
4867
5055
"serde",
4868
-
"thiserror 2.0.12",
5056
+
"thiserror 2.0.16",
4869
5057
]
4870
5058
4871
5059
[[package]]
···
5008
5196
dependencies = [
5009
5197
"num-bigint",
5010
5198
"num-traits",
5011
-
"thiserror 2.0.12",
5199
+
"thiserror 2.0.16",
5012
5200
"time",
5013
5201
]
5014
5202
···
5050
5238
"rustls 0.23.31",
5051
5239
"serde",
5052
5240
"serde_json",
5053
-
"thiserror 2.0.12",
5241
+
"thiserror 2.0.16",
5054
5242
"time",
5055
5243
"tokio",
5056
5244
"tokio-util",
···
5161
5349
"serde",
5162
5350
"serde_json",
5163
5351
"serde_qs",
5164
-
"thiserror 2.0.12",
5352
+
"thiserror 2.0.16",
5165
5353
"tinyjson",
5166
5354
"tokio",
5167
5355
"tokio-tungstenite 0.27.0",
···
5327
5515
5328
5516
[[package]]
5329
5517
name = "thiserror"
5330
-
version = "2.0.12"
5518
+
version = "2.0.16"
5331
5519
source = "registry+https://github.com/rust-lang/crates.io-index"
5332
-
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
5520
+
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
5333
5521
dependencies = [
5334
-
"thiserror-impl 2.0.12",
5522
+
"thiserror-impl 2.0.16",
5335
5523
]
5336
5524
5337
5525
[[package]]
···
5347
5535
5348
5536
[[package]]
5349
5537
name = "thiserror-impl"
5350
-
version = "2.0.12"
5538
+
version = "2.0.16"
5351
5539
source = "registry+https://github.com/rust-lang/crates.io-index"
5352
-
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
5540
+
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
5353
5541
dependencies = [
5354
5542
"proc-macro2",
5355
5543
"quote",
···
5452
5640
5453
5641
[[package]]
5454
5642
name = "tokio"
5455
-
version = "1.47.0"
5643
+
version = "1.47.1"
5456
5644
source = "registry+https://github.com/rust-lang/crates.io-index"
5457
-
checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35"
5645
+
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
5458
5646
dependencies = [
5459
5647
"backtrace",
5460
5648
"bytes",
···
5695
5883
5696
5884
[[package]]
5697
5885
name = "tracing-subscriber"
5698
-
version = "0.3.19"
5886
+
version = "0.3.20"
5699
5887
source = "registry+https://github.com/rust-lang/crates.io-index"
5700
-
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
5888
+
checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
5701
5889
dependencies = [
5702
5890
"matchers",
5703
5891
"nu-ansi-term",
5704
5892
"once_cell",
5705
-
"regex",
5893
+
"regex-automata",
5706
5894
"sharded-slab",
5707
5895
"smallvec",
5708
5896
"thread_local",
···
5742
5930
"native-tls",
5743
5931
"rand 0.9.1",
5744
5932
"sha1",
5745
-
"thiserror 2.0.12",
5933
+
"thiserror 2.0.16",
5746
5934
"url",
5747
5935
"utf-8",
5748
5936
]
···
5760
5948
"log",
5761
5949
"rand 0.9.1",
5762
5950
"sha1",
5763
-
"thiserror 2.0.12",
5951
+
"thiserror 2.0.16",
5764
5952
"utf-8",
5765
5953
]
5766
5954
···
5813
6001
"serde_qs",
5814
6002
"sha2",
5815
6003
"tempfile",
5816
-
"thiserror 2.0.12",
6004
+
"thiserror 2.0.16",
5817
6005
"tikv-jemallocator",
5818
6006
"tokio",
5819
6007
"tokio-util",
···
6147
6335
"reqwest",
6148
6336
"serde",
6149
6337
"serde_json",
6150
-
"thiserror 2.0.12",
6338
+
"thiserror 2.0.16",
6151
6339
"tokio",
6152
6340
"tokio-util",
6153
6341
"url",
···
6540
6728
"nom",
6541
6729
"oid-registry",
6542
6730
"rusticata-macros",
6543
-
"thiserror 2.0.12",
6731
+
"thiserror 2.0.16",
6544
6732
"time",
6545
6733
]
6546
6734
···
6651
6839
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
6652
6840
dependencies = [
6653
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",
6654
6854
]
6655
6855
6656
6856
[[package]]
+2
Cargo.toml
+2
Cargo.toml
+8
-1
Makefile
+8
-1
Makefile
···
5
5
cargo test --all-features
6
6
7
7
fmt:
8
-
cargo fmt --package links --package constellation --package ufos --package spacedust --package who-am-i --package slingshot
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
9
16
cargo +nightly fmt --package jetstream
10
17
11
18
clippy:
+19
pocket/Cargo.toml
+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
+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
+5
pocket/src/lib.rs
+8
pocket/src/main.rs
+8
pocket/src/main.rs
+202
pocket/src/server.rs
+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
+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
+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>
+12
reflector/Cargo.toml
+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
+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
+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
+
}