Magazi is a content distribution platform that gates access to files using ATProtocol (Bluesky) identity and cryptographic proofs. download.ngerakines.me/
atprotocol appview atprotocol-attestations
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

feature: magazi

Nick Gerakines 42c947dd

+6521
+40
.gitignore
··· 1 + # Rust build artifacts 2 + /target/ 3 + 4 + # IDE 5 + .idea/ 6 + .vscode/ 7 + *.swp 8 + *.swo 9 + *~ 10 + 11 + # Environment files 12 + .env 13 + .env.local 14 + .env.*.local 15 + 16 + # Data directories 17 + /data/ 18 + *.db 19 + *.sqlite 20 + 21 + # Logs 22 + *.log 23 + logs/ 24 + 25 + # OS files 26 + .DS_Store 27 + Thumbs.db 28 + 29 + # Docker 30 + .docker/ 31 + 32 + # Coverage 33 + *.profraw 34 + *.profdata 35 + coverage/ 36 + tmp/ 37 + 38 + node_modules/ 39 + 40 + /.claude/
+3825
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "aead" 7 + version = "0.5.2" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 10 + dependencies = [ 11 + "crypto-common", 12 + "generic-array", 13 + ] 14 + 15 + [[package]] 16 + name = "aes" 17 + version = "0.8.4" 18 + source = "registry+https://github.com/rust-lang/crates.io-index" 19 + checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 20 + dependencies = [ 21 + "cfg-if", 22 + "cipher", 23 + "cpufeatures", 24 + ] 25 + 26 + [[package]] 27 + name = "aes-gcm" 28 + version = "0.10.3" 29 + source = "registry+https://github.com/rust-lang/crates.io-index" 30 + checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" 31 + dependencies = [ 32 + "aead", 33 + "aes", 34 + "cipher", 35 + "ctr", 36 + "ghash", 37 + "subtle", 38 + ] 39 + 40 + [[package]] 41 + name = "aho-corasick" 42 + version = "1.1.4" 43 + source = "registry+https://github.com/rust-lang/crates.io-index" 44 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 45 + dependencies = [ 46 + "memchr", 47 + ] 48 + 49 + [[package]] 50 + name = "allocator-api2" 51 + version = "0.2.21" 52 + source = "registry+https://github.com/rust-lang/crates.io-index" 53 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 54 + 55 + [[package]] 56 + name = "android_system_properties" 57 + version = "0.1.5" 58 + source = "registry+https://github.com/rust-lang/crates.io-index" 59 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 60 + dependencies = [ 61 + "libc", 62 + ] 63 + 64 + [[package]] 65 + name = "anyhow" 66 + version = "1.0.100" 67 + source = "registry+https://github.com/rust-lang/crates.io-index" 68 + checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" 69 + 70 + [[package]] 71 + name = "arrayref" 72 + version = "0.3.9" 73 + source = "registry+https://github.com/rust-lang/crates.io-index" 74 + checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" 75 + 76 + [[package]] 77 + name = "arrayvec" 78 + version = "0.7.6" 79 + source = "registry+https://github.com/rust-lang/crates.io-index" 80 + checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 81 + 82 + [[package]] 83 + name = "async-lock" 84 + version = "3.4.2" 85 + source = "registry+https://github.com/rust-lang/crates.io-index" 86 + checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" 87 + dependencies = [ 88 + "event-listener", 89 + "event-listener-strategy", 90 + "pin-project-lite", 91 + ] 92 + 93 + [[package]] 94 + name = "async-trait" 95 + version = "0.1.89" 96 + source = "registry+https://github.com/rust-lang/crates.io-index" 97 + checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" 98 + dependencies = [ 99 + "proc-macro2", 100 + "quote", 101 + "syn", 102 + ] 103 + 104 + [[package]] 105 + name = "atomic-waker" 106 + version = "1.1.2" 107 + source = "registry+https://github.com/rust-lang/crates.io-index" 108 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 109 + 110 + [[package]] 111 + name = "atproto-attestation" 112 + version = "0.13.0" 113 + source = "git+https://tangled.sh/@smokesignal.events/atproto-identity-rs#e3b73a2a28b272a8fd38fe92c0bf454246bb0e81" 114 + dependencies = [ 115 + "anyhow", 116 + "atproto-client", 117 + "atproto-identity", 118 + "atproto-record", 119 + "base64", 120 + "cid", 121 + "elliptic-curve", 122 + "k256", 123 + "multihash", 124 + "p256", 125 + "serde", 126 + "serde_ipld_dagcbor", 127 + "serde_json", 128 + "sha2", 129 + "thiserror 2.0.17", 130 + ] 131 + 132 + [[package]] 133 + name = "atproto-client" 134 + version = "0.13.0" 135 + source = "git+https://tangled.sh/@smokesignal.events/atproto-identity-rs#e3b73a2a28b272a8fd38fe92c0bf454246bb0e81" 136 + dependencies = [ 137 + "anyhow", 138 + "async-trait", 139 + "atproto-identity", 140 + "atproto-oauth", 141 + "atproto-record", 142 + "bytes", 143 + "reqwest", 144 + "reqwest-chain", 145 + "reqwest-middleware", 146 + "serde", 147 + "serde_json", 148 + "thiserror 2.0.17", 149 + "tokio", 150 + "tracing", 151 + "urlencoding", 152 + ] 153 + 154 + [[package]] 155 + name = "atproto-identity" 156 + version = "0.13.0" 157 + source = "git+https://tangled.sh/@smokesignal.events/atproto-identity-rs#e3b73a2a28b272a8fd38fe92c0bf454246bb0e81" 158 + dependencies = [ 159 + "anyhow", 160 + "async-trait", 161 + "ecdsa", 162 + "elliptic-curve", 163 + "hickory-resolver", 164 + "k256", 165 + "lru", 166 + "multibase", 167 + "p256", 168 + "p384", 169 + "rand 0.8.5", 170 + "reqwest", 171 + "serde", 172 + "serde_ipld_dagcbor", 173 + "serde_json", 174 + "thiserror 2.0.17", 175 + "tokio", 176 + "tracing", 177 + "url", 178 + "urlencoding", 179 + ] 180 + 181 + [[package]] 182 + name = "atproto-oauth" 183 + version = "0.13.0" 184 + source = "git+https://tangled.sh/@smokesignal.events/atproto-identity-rs#e3b73a2a28b272a8fd38fe92c0bf454246bb0e81" 185 + dependencies = [ 186 + "anyhow", 187 + "async-trait", 188 + "atproto-identity", 189 + "base64", 190 + "chrono", 191 + "ecdsa", 192 + "elliptic-curve", 193 + "k256", 194 + "lru", 195 + "multibase", 196 + "p256", 197 + "p384", 198 + "rand 0.8.5", 199 + "reqwest", 200 + "reqwest-chain", 201 + "reqwest-middleware", 202 + "serde", 203 + "serde_ipld_dagcbor", 204 + "serde_json", 205 + "sha2", 206 + "thiserror 2.0.17", 207 + "tokio", 208 + "tracing", 209 + "ulid", 210 + ] 211 + 212 + [[package]] 213 + name = "atproto-record" 214 + version = "0.13.0" 215 + source = "git+https://tangled.sh/@smokesignal.events/atproto-identity-rs#e3b73a2a28b272a8fd38fe92c0bf454246bb0e81" 216 + dependencies = [ 217 + "anyhow", 218 + "atproto-identity", 219 + "base64", 220 + "chrono", 221 + "cid", 222 + "multihash", 223 + "rand 0.8.5", 224 + "serde", 225 + "serde_ipld_dagcbor", 226 + "serde_json", 227 + "sha2", 228 + "thiserror 2.0.17", 229 + ] 230 + 231 + [[package]] 232 + name = "autocfg" 233 + version = "1.5.0" 234 + source = "registry+https://github.com/rust-lang/crates.io-index" 235 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 236 + 237 + [[package]] 238 + name = "axum" 239 + version = "0.8.8" 240 + source = "registry+https://github.com/rust-lang/crates.io-index" 241 + checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" 242 + dependencies = [ 243 + "axum-core", 244 + "axum-macros", 245 + "bytes", 246 + "form_urlencoded", 247 + "futures-util", 248 + "http", 249 + "http-body", 250 + "http-body-util", 251 + "hyper", 252 + "hyper-util", 253 + "itoa", 254 + "matchit", 255 + "memchr", 256 + "mime", 257 + "percent-encoding", 258 + "pin-project-lite", 259 + "serde_core", 260 + "serde_json", 261 + "serde_path_to_error", 262 + "serde_urlencoded", 263 + "sync_wrapper", 264 + "tokio", 265 + "tower", 266 + "tower-layer", 267 + "tower-service", 268 + "tracing", 269 + ] 270 + 271 + [[package]] 272 + name = "axum-core" 273 + version = "0.5.6" 274 + source = "registry+https://github.com/rust-lang/crates.io-index" 275 + checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" 276 + dependencies = [ 277 + "bytes", 278 + "futures-core", 279 + "http", 280 + "http-body", 281 + "http-body-util", 282 + "mime", 283 + "pin-project-lite", 284 + "sync_wrapper", 285 + "tower-layer", 286 + "tower-service", 287 + "tracing", 288 + ] 289 + 290 + [[package]] 291 + name = "axum-extra" 292 + version = "0.10.3" 293 + source = "registry+https://github.com/rust-lang/crates.io-index" 294 + checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" 295 + dependencies = [ 296 + "axum", 297 + "axum-core", 298 + "bytes", 299 + "cookie", 300 + "futures-util", 301 + "headers", 302 + "http", 303 + "http-body", 304 + "http-body-util", 305 + "mime", 306 + "pin-project-lite", 307 + "rustversion", 308 + "serde_core", 309 + "tower-layer", 310 + "tower-service", 311 + "tracing", 312 + ] 313 + 314 + [[package]] 315 + name = "axum-macros" 316 + version = "0.5.0" 317 + source = "registry+https://github.com/rust-lang/crates.io-index" 318 + checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" 319 + dependencies = [ 320 + "proc-macro2", 321 + "quote", 322 + "syn", 323 + ] 324 + 325 + [[package]] 326 + name = "base-x" 327 + version = "0.2.11" 328 + source = "registry+https://github.com/rust-lang/crates.io-index" 329 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 330 + 331 + [[package]] 332 + name = "base16ct" 333 + version = "0.2.0" 334 + source = "registry+https://github.com/rust-lang/crates.io-index" 335 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 336 + 337 + [[package]] 338 + name = "base256emoji" 339 + version = "1.0.2" 340 + source = "registry+https://github.com/rust-lang/crates.io-index" 341 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 342 + dependencies = [ 343 + "const-str", 344 + "match-lookup", 345 + ] 346 + 347 + [[package]] 348 + name = "base64" 349 + version = "0.22.1" 350 + source = "registry+https://github.com/rust-lang/crates.io-index" 351 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 352 + 353 + [[package]] 354 + name = "base64ct" 355 + version = "1.8.3" 356 + source = "registry+https://github.com/rust-lang/crates.io-index" 357 + checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" 358 + 359 + [[package]] 360 + name = "bitflags" 361 + version = "1.3.2" 362 + source = "registry+https://github.com/rust-lang/crates.io-index" 363 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 364 + 365 + [[package]] 366 + name = "bitflags" 367 + version = "2.10.0" 368 + source = "registry+https://github.com/rust-lang/crates.io-index" 369 + checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" 370 + 371 + [[package]] 372 + name = "blake2b_simd" 373 + version = "1.0.4" 374 + source = "registry+https://github.com/rust-lang/crates.io-index" 375 + checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" 376 + dependencies = [ 377 + "arrayref", 378 + "arrayvec", 379 + "constant_time_eq", 380 + ] 381 + 382 + [[package]] 383 + name = "blake2s_simd" 384 + version = "1.0.4" 385 + source = "registry+https://github.com/rust-lang/crates.io-index" 386 + checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" 387 + dependencies = [ 388 + "arrayref", 389 + "arrayvec", 390 + "constant_time_eq", 391 + ] 392 + 393 + [[package]] 394 + name = "blake3" 395 + version = "1.8.3" 396 + source = "registry+https://github.com/rust-lang/crates.io-index" 397 + checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" 398 + dependencies = [ 399 + "arrayref", 400 + "arrayvec", 401 + "cc", 402 + "cfg-if", 403 + "constant_time_eq", 404 + "cpufeatures", 405 + ] 406 + 407 + [[package]] 408 + name = "block-buffer" 409 + version = "0.10.4" 410 + source = "registry+https://github.com/rust-lang/crates.io-index" 411 + checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 412 + dependencies = [ 413 + "generic-array", 414 + ] 415 + 416 + [[package]] 417 + name = "bumpalo" 418 + version = "3.19.1" 419 + source = "registry+https://github.com/rust-lang/crates.io-index" 420 + checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" 421 + 422 + [[package]] 423 + name = "byteorder" 424 + version = "1.5.0" 425 + source = "registry+https://github.com/rust-lang/crates.io-index" 426 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 427 + 428 + [[package]] 429 + name = "bytes" 430 + version = "1.11.0" 431 + source = "registry+https://github.com/rust-lang/crates.io-index" 432 + checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" 433 + 434 + [[package]] 435 + name = "cbor4ii" 436 + version = "0.2.14" 437 + source = "registry+https://github.com/rust-lang/crates.io-index" 438 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 439 + dependencies = [ 440 + "serde", 441 + ] 442 + 443 + [[package]] 444 + name = "cc" 445 + version = "1.2.52" 446 + source = "registry+https://github.com/rust-lang/crates.io-index" 447 + checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" 448 + dependencies = [ 449 + "find-msvc-tools", 450 + "shlex", 451 + ] 452 + 453 + [[package]] 454 + name = "cfg-if" 455 + version = "1.0.4" 456 + source = "registry+https://github.com/rust-lang/crates.io-index" 457 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 458 + 459 + [[package]] 460 + name = "cfg_aliases" 461 + version = "0.2.1" 462 + source = "registry+https://github.com/rust-lang/crates.io-index" 463 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 464 + 465 + [[package]] 466 + name = "chrono" 467 + version = "0.4.43" 468 + source = "registry+https://github.com/rust-lang/crates.io-index" 469 + checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" 470 + dependencies = [ 471 + "iana-time-zone", 472 + "js-sys", 473 + "num-traits", 474 + "serde", 475 + "wasm-bindgen", 476 + "windows-link", 477 + ] 478 + 479 + [[package]] 480 + name = "cid" 481 + version = "0.11.1" 482 + source = "registry+https://github.com/rust-lang/crates.io-index" 483 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 484 + dependencies = [ 485 + "core2", 486 + "multibase", 487 + "multihash", 488 + "serde", 489 + "serde_bytes", 490 + "unsigned-varint", 491 + ] 492 + 493 + [[package]] 494 + name = "cipher" 495 + version = "0.4.4" 496 + source = "registry+https://github.com/rust-lang/crates.io-index" 497 + checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 498 + dependencies = [ 499 + "crypto-common", 500 + "inout", 501 + ] 502 + 503 + [[package]] 504 + name = "concurrent-queue" 505 + version = "2.5.0" 506 + source = "registry+https://github.com/rust-lang/crates.io-index" 507 + checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" 508 + dependencies = [ 509 + "crossbeam-utils", 510 + ] 511 + 512 + [[package]] 513 + name = "const-oid" 514 + version = "0.9.6" 515 + source = "registry+https://github.com/rust-lang/crates.io-index" 516 + checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 517 + 518 + [[package]] 519 + name = "const-str" 520 + version = "0.4.3" 521 + source = "registry+https://github.com/rust-lang/crates.io-index" 522 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 523 + 524 + [[package]] 525 + name = "constant_time_eq" 526 + version = "0.4.2" 527 + source = "registry+https://github.com/rust-lang/crates.io-index" 528 + checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" 529 + 530 + [[package]] 531 + name = "cookie" 532 + version = "0.18.1" 533 + source = "registry+https://github.com/rust-lang/crates.io-index" 534 + checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" 535 + dependencies = [ 536 + "aes-gcm", 537 + "base64", 538 + "hmac", 539 + "percent-encoding", 540 + "rand 0.8.5", 541 + "sha2", 542 + "subtle", 543 + "time", 544 + "version_check", 545 + ] 546 + 547 + [[package]] 548 + name = "core-foundation" 549 + version = "0.9.4" 550 + source = "registry+https://github.com/rust-lang/crates.io-index" 551 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 552 + dependencies = [ 553 + "core-foundation-sys", 554 + "libc", 555 + ] 556 + 557 + [[package]] 558 + name = "core-foundation-sys" 559 + version = "0.8.7" 560 + source = "registry+https://github.com/rust-lang/crates.io-index" 561 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 562 + 563 + [[package]] 564 + name = "core2" 565 + version = "0.4.0" 566 + source = "registry+https://github.com/rust-lang/crates.io-index" 567 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 568 + dependencies = [ 569 + "memchr", 570 + ] 571 + 572 + [[package]] 573 + name = "cpufeatures" 574 + version = "0.2.17" 575 + source = "registry+https://github.com/rust-lang/crates.io-index" 576 + checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 577 + dependencies = [ 578 + "libc", 579 + ] 580 + 581 + [[package]] 582 + name = "critical-section" 583 + version = "1.2.0" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 586 + 587 + [[package]] 588 + name = "crossbeam-channel" 589 + version = "0.5.15" 590 + source = "registry+https://github.com/rust-lang/crates.io-index" 591 + checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 592 + dependencies = [ 593 + "crossbeam-utils", 594 + ] 595 + 596 + [[package]] 597 + name = "crossbeam-epoch" 598 + version = "0.9.18" 599 + source = "registry+https://github.com/rust-lang/crates.io-index" 600 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 601 + dependencies = [ 602 + "crossbeam-utils", 603 + ] 604 + 605 + [[package]] 606 + name = "crossbeam-utils" 607 + version = "0.8.21" 608 + source = "registry+https://github.com/rust-lang/crates.io-index" 609 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 610 + 611 + [[package]] 612 + name = "crypto-bigint" 613 + version = "0.5.5" 614 + source = "registry+https://github.com/rust-lang/crates.io-index" 615 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 616 + dependencies = [ 617 + "generic-array", 618 + "rand_core 0.6.4", 619 + "subtle", 620 + "zeroize", 621 + ] 622 + 623 + [[package]] 624 + name = "crypto-common" 625 + version = "0.1.6" 626 + source = "registry+https://github.com/rust-lang/crates.io-index" 627 + checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 628 + dependencies = [ 629 + "generic-array", 630 + "rand_core 0.6.4", 631 + "typenum", 632 + ] 633 + 634 + [[package]] 635 + name = "ctr" 636 + version = "0.9.2" 637 + source = "registry+https://github.com/rust-lang/crates.io-index" 638 + checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 639 + dependencies = [ 640 + "cipher", 641 + ] 642 + 643 + [[package]] 644 + name = "data-encoding" 645 + version = "2.10.0" 646 + source = "registry+https://github.com/rust-lang/crates.io-index" 647 + checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" 648 + 649 + [[package]] 650 + name = "data-encoding-macro" 651 + version = "0.1.19" 652 + source = "registry+https://github.com/rust-lang/crates.io-index" 653 + checksum = "8142a83c17aa9461d637e649271eae18bf2edd00e91f2e105df36c3c16355bdb" 654 + dependencies = [ 655 + "data-encoding", 656 + "data-encoding-macro-internal", 657 + ] 658 + 659 + [[package]] 660 + name = "data-encoding-macro-internal" 661 + version = "0.1.17" 662 + source = "registry+https://github.com/rust-lang/crates.io-index" 663 + checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" 664 + dependencies = [ 665 + "data-encoding", 666 + "syn", 667 + ] 668 + 669 + [[package]] 670 + name = "datalogic-rs" 671 + version = "4.0.5" 672 + source = "registry+https://github.com/rust-lang/crates.io-index" 673 + checksum = "9ff48883efc0d97cde7b04c28a235d85be0b57511cb60b66e44166ec4a68d21f" 674 + dependencies = [ 675 + "chrono", 676 + "regex", 677 + "serde_json", 678 + "smallvec", 679 + ] 680 + 681 + [[package]] 682 + name = "der" 683 + version = "0.7.10" 684 + source = "registry+https://github.com/rust-lang/crates.io-index" 685 + checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" 686 + dependencies = [ 687 + "const-oid", 688 + "pem-rfc7468", 689 + "zeroize", 690 + ] 691 + 692 + [[package]] 693 + name = "deranged" 694 + version = "0.5.5" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 697 + dependencies = [ 698 + "powerfmt", 699 + ] 700 + 701 + [[package]] 702 + name = "digest" 703 + version = "0.10.7" 704 + source = "registry+https://github.com/rust-lang/crates.io-index" 705 + checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 706 + dependencies = [ 707 + "block-buffer", 708 + "const-oid", 709 + "crypto-common", 710 + "subtle", 711 + ] 712 + 713 + [[package]] 714 + name = "displaydoc" 715 + version = "0.2.5" 716 + source = "registry+https://github.com/rust-lang/crates.io-index" 717 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 718 + dependencies = [ 719 + "proc-macro2", 720 + "quote", 721 + "syn", 722 + ] 723 + 724 + [[package]] 725 + name = "ecdsa" 726 + version = "0.16.9" 727 + source = "registry+https://github.com/rust-lang/crates.io-index" 728 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 729 + dependencies = [ 730 + "der", 731 + "digest", 732 + "elliptic-curve", 733 + "rfc6979", 734 + "serdect", 735 + "signature", 736 + "spki", 737 + ] 738 + 739 + [[package]] 740 + name = "elliptic-curve" 741 + version = "0.13.8" 742 + source = "registry+https://github.com/rust-lang/crates.io-index" 743 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 744 + dependencies = [ 745 + "base16ct", 746 + "base64ct", 747 + "crypto-bigint", 748 + "digest", 749 + "ff", 750 + "generic-array", 751 + "group", 752 + "hkdf", 753 + "pem-rfc7468", 754 + "pkcs8", 755 + "rand_core 0.6.4", 756 + "sec1", 757 + "serde_json", 758 + "serdect", 759 + "subtle", 760 + "zeroize", 761 + ] 762 + 763 + [[package]] 764 + name = "encoding_rs" 765 + version = "0.8.35" 766 + source = "registry+https://github.com/rust-lang/crates.io-index" 767 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 768 + dependencies = [ 769 + "cfg-if", 770 + ] 771 + 772 + [[package]] 773 + name = "enum-as-inner" 774 + version = "0.6.1" 775 + source = "registry+https://github.com/rust-lang/crates.io-index" 776 + checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" 777 + dependencies = [ 778 + "heck", 779 + "proc-macro2", 780 + "quote", 781 + "syn", 782 + ] 783 + 784 + [[package]] 785 + name = "equivalent" 786 + version = "1.0.2" 787 + source = "registry+https://github.com/rust-lang/crates.io-index" 788 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 789 + 790 + [[package]] 791 + name = "errno" 792 + version = "0.3.14" 793 + source = "registry+https://github.com/rust-lang/crates.io-index" 794 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 795 + dependencies = [ 796 + "libc", 797 + "windows-sys 0.61.2", 798 + ] 799 + 800 + [[package]] 801 + name = "event-listener" 802 + version = "5.4.1" 803 + source = "registry+https://github.com/rust-lang/crates.io-index" 804 + checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" 805 + dependencies = [ 806 + "concurrent-queue", 807 + "parking", 808 + "pin-project-lite", 809 + ] 810 + 811 + [[package]] 812 + name = "event-listener-strategy" 813 + version = "0.5.4" 814 + source = "registry+https://github.com/rust-lang/crates.io-index" 815 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 816 + dependencies = [ 817 + "event-listener", 818 + "pin-project-lite", 819 + ] 820 + 821 + [[package]] 822 + name = "fastrand" 823 + version = "2.3.0" 824 + source = "registry+https://github.com/rust-lang/crates.io-index" 825 + checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 826 + 827 + [[package]] 828 + name = "ff" 829 + version = "0.13.1" 830 + source = "registry+https://github.com/rust-lang/crates.io-index" 831 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 832 + dependencies = [ 833 + "rand_core 0.6.4", 834 + "subtle", 835 + ] 836 + 837 + [[package]] 838 + name = "find-msvc-tools" 839 + version = "0.1.7" 840 + source = "registry+https://github.com/rust-lang/crates.io-index" 841 + checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" 842 + 843 + [[package]] 844 + name = "fnv" 845 + version = "1.0.7" 846 + source = "registry+https://github.com/rust-lang/crates.io-index" 847 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 848 + 849 + [[package]] 850 + name = "foldhash" 851 + version = "0.1.5" 852 + source = "registry+https://github.com/rust-lang/crates.io-index" 853 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 854 + 855 + [[package]] 856 + name = "foreign-types" 857 + version = "0.3.2" 858 + source = "registry+https://github.com/rust-lang/crates.io-index" 859 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 860 + dependencies = [ 861 + "foreign-types-shared", 862 + ] 863 + 864 + [[package]] 865 + name = "foreign-types-shared" 866 + version = "0.1.1" 867 + source = "registry+https://github.com/rust-lang/crates.io-index" 868 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 869 + 870 + [[package]] 871 + name = "form_urlencoded" 872 + version = "1.2.2" 873 + source = "registry+https://github.com/rust-lang/crates.io-index" 874 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 875 + dependencies = [ 876 + "percent-encoding", 877 + ] 878 + 879 + [[package]] 880 + name = "futures-channel" 881 + version = "0.3.31" 882 + source = "registry+https://github.com/rust-lang/crates.io-index" 883 + checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 884 + dependencies = [ 885 + "futures-core", 886 + ] 887 + 888 + [[package]] 889 + name = "futures-core" 890 + version = "0.3.31" 891 + source = "registry+https://github.com/rust-lang/crates.io-index" 892 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 893 + 894 + [[package]] 895 + name = "futures-io" 896 + version = "0.3.31" 897 + source = "registry+https://github.com/rust-lang/crates.io-index" 898 + checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 899 + 900 + [[package]] 901 + name = "futures-macro" 902 + version = "0.3.31" 903 + source = "registry+https://github.com/rust-lang/crates.io-index" 904 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 905 + dependencies = [ 906 + "proc-macro2", 907 + "quote", 908 + "syn", 909 + ] 910 + 911 + [[package]] 912 + name = "futures-sink" 913 + version = "0.3.31" 914 + source = "registry+https://github.com/rust-lang/crates.io-index" 915 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 916 + 917 + [[package]] 918 + name = "futures-task" 919 + version = "0.3.31" 920 + source = "registry+https://github.com/rust-lang/crates.io-index" 921 + checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 922 + 923 + [[package]] 924 + name = "futures-util" 925 + version = "0.3.31" 926 + source = "registry+https://github.com/rust-lang/crates.io-index" 927 + checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 928 + dependencies = [ 929 + "futures-core", 930 + "futures-macro", 931 + "futures-task", 932 + "pin-project-lite", 933 + "pin-utils", 934 + "slab", 935 + ] 936 + 937 + [[package]] 938 + name = "generic-array" 939 + version = "0.14.9" 940 + source = "registry+https://github.com/rust-lang/crates.io-index" 941 + checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" 942 + dependencies = [ 943 + "typenum", 944 + "version_check", 945 + "zeroize", 946 + ] 947 + 948 + [[package]] 949 + name = "getrandom" 950 + version = "0.2.17" 951 + source = "registry+https://github.com/rust-lang/crates.io-index" 952 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 953 + dependencies = [ 954 + "cfg-if", 955 + "js-sys", 956 + "libc", 957 + "wasi", 958 + "wasm-bindgen", 959 + ] 960 + 961 + [[package]] 962 + name = "getrandom" 963 + version = "0.3.4" 964 + source = "registry+https://github.com/rust-lang/crates.io-index" 965 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 966 + dependencies = [ 967 + "cfg-if", 968 + "js-sys", 969 + "libc", 970 + "r-efi", 971 + "wasip2", 972 + "wasm-bindgen", 973 + ] 974 + 975 + [[package]] 976 + name = "ghash" 977 + version = "0.5.1" 978 + source = "registry+https://github.com/rust-lang/crates.io-index" 979 + checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" 980 + dependencies = [ 981 + "opaque-debug", 982 + "polyval", 983 + ] 984 + 985 + [[package]] 986 + name = "group" 987 + version = "0.13.0" 988 + source = "registry+https://github.com/rust-lang/crates.io-index" 989 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 990 + dependencies = [ 991 + "ff", 992 + "rand_core 0.6.4", 993 + "subtle", 994 + ] 995 + 996 + [[package]] 997 + name = "h2" 998 + version = "0.4.13" 999 + source = "registry+https://github.com/rust-lang/crates.io-index" 1000 + checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" 1001 + dependencies = [ 1002 + "atomic-waker", 1003 + "bytes", 1004 + "fnv", 1005 + "futures-core", 1006 + "futures-sink", 1007 + "http", 1008 + "indexmap", 1009 + "slab", 1010 + "tokio", 1011 + "tokio-util", 1012 + "tracing", 1013 + ] 1014 + 1015 + [[package]] 1016 + name = "hashbrown" 1017 + version = "0.15.5" 1018 + source = "registry+https://github.com/rust-lang/crates.io-index" 1019 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 1020 + dependencies = [ 1021 + "allocator-api2", 1022 + "equivalent", 1023 + "foldhash", 1024 + ] 1025 + 1026 + [[package]] 1027 + name = "hashbrown" 1028 + version = "0.16.1" 1029 + source = "registry+https://github.com/rust-lang/crates.io-index" 1030 + checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" 1031 + 1032 + [[package]] 1033 + name = "headers" 1034 + version = "0.4.1" 1035 + source = "registry+https://github.com/rust-lang/crates.io-index" 1036 + checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" 1037 + dependencies = [ 1038 + "base64", 1039 + "bytes", 1040 + "headers-core", 1041 + "http", 1042 + "httpdate", 1043 + "mime", 1044 + "sha1", 1045 + ] 1046 + 1047 + [[package]] 1048 + name = "headers-core" 1049 + version = "0.3.0" 1050 + source = "registry+https://github.com/rust-lang/crates.io-index" 1051 + checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" 1052 + dependencies = [ 1053 + "http", 1054 + ] 1055 + 1056 + [[package]] 1057 + name = "heck" 1058 + version = "0.5.0" 1059 + source = "registry+https://github.com/rust-lang/crates.io-index" 1060 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 1061 + 1062 + [[package]] 1063 + name = "hickory-proto" 1064 + version = "0.25.2" 1065 + source = "registry+https://github.com/rust-lang/crates.io-index" 1066 + checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" 1067 + dependencies = [ 1068 + "async-trait", 1069 + "cfg-if", 1070 + "data-encoding", 1071 + "enum-as-inner", 1072 + "futures-channel", 1073 + "futures-io", 1074 + "futures-util", 1075 + "idna", 1076 + "ipnet", 1077 + "once_cell", 1078 + "rand 0.9.2", 1079 + "ring", 1080 + "thiserror 2.0.17", 1081 + "tinyvec", 1082 + "tokio", 1083 + "tracing", 1084 + "url", 1085 + ] 1086 + 1087 + [[package]] 1088 + name = "hickory-resolver" 1089 + version = "0.25.2" 1090 + source = "registry+https://github.com/rust-lang/crates.io-index" 1091 + checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" 1092 + dependencies = [ 1093 + "cfg-if", 1094 + "futures-util", 1095 + "hickory-proto", 1096 + "ipconfig", 1097 + "moka", 1098 + "once_cell", 1099 + "parking_lot", 1100 + "rand 0.9.2", 1101 + "resolv-conf", 1102 + "smallvec", 1103 + "thiserror 2.0.17", 1104 + "tokio", 1105 + "tracing", 1106 + ] 1107 + 1108 + [[package]] 1109 + name = "hkdf" 1110 + version = "0.12.4" 1111 + source = "registry+https://github.com/rust-lang/crates.io-index" 1112 + checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" 1113 + dependencies = [ 1114 + "hmac", 1115 + ] 1116 + 1117 + [[package]] 1118 + name = "hmac" 1119 + version = "0.12.1" 1120 + source = "registry+https://github.com/rust-lang/crates.io-index" 1121 + checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 1122 + dependencies = [ 1123 + "digest", 1124 + ] 1125 + 1126 + [[package]] 1127 + name = "http" 1128 + version = "1.4.0" 1129 + source = "registry+https://github.com/rust-lang/crates.io-index" 1130 + checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" 1131 + dependencies = [ 1132 + "bytes", 1133 + "itoa", 1134 + ] 1135 + 1136 + [[package]] 1137 + name = "http-body" 1138 + version = "1.0.1" 1139 + source = "registry+https://github.com/rust-lang/crates.io-index" 1140 + checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 1141 + dependencies = [ 1142 + "bytes", 1143 + "http", 1144 + ] 1145 + 1146 + [[package]] 1147 + name = "http-body-util" 1148 + version = "0.1.3" 1149 + source = "registry+https://github.com/rust-lang/crates.io-index" 1150 + checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 1151 + dependencies = [ 1152 + "bytes", 1153 + "futures-core", 1154 + "http", 1155 + "http-body", 1156 + "pin-project-lite", 1157 + ] 1158 + 1159 + [[package]] 1160 + name = "http-range-header" 1161 + version = "0.4.2" 1162 + source = "registry+https://github.com/rust-lang/crates.io-index" 1163 + checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" 1164 + 1165 + [[package]] 1166 + name = "httparse" 1167 + version = "1.10.1" 1168 + source = "registry+https://github.com/rust-lang/crates.io-index" 1169 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 1170 + 1171 + [[package]] 1172 + name = "httpdate" 1173 + version = "1.0.3" 1174 + source = "registry+https://github.com/rust-lang/crates.io-index" 1175 + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 1176 + 1177 + [[package]] 1178 + name = "hyper" 1179 + version = "1.8.1" 1180 + source = "registry+https://github.com/rust-lang/crates.io-index" 1181 + checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" 1182 + dependencies = [ 1183 + "atomic-waker", 1184 + "bytes", 1185 + "futures-channel", 1186 + "futures-core", 1187 + "h2", 1188 + "http", 1189 + "http-body", 1190 + "httparse", 1191 + "httpdate", 1192 + "itoa", 1193 + "pin-project-lite", 1194 + "pin-utils", 1195 + "smallvec", 1196 + "tokio", 1197 + "want", 1198 + ] 1199 + 1200 + [[package]] 1201 + name = "hyper-rustls" 1202 + version = "0.27.7" 1203 + source = "registry+https://github.com/rust-lang/crates.io-index" 1204 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1205 + dependencies = [ 1206 + "http", 1207 + "hyper", 1208 + "hyper-util", 1209 + "rustls", 1210 + "rustls-pki-types", 1211 + "tokio", 1212 + "tokio-rustls", 1213 + "tower-service", 1214 + "webpki-roots", 1215 + ] 1216 + 1217 + [[package]] 1218 + name = "hyper-tls" 1219 + version = "0.6.0" 1220 + source = "registry+https://github.com/rust-lang/crates.io-index" 1221 + checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 1222 + dependencies = [ 1223 + "bytes", 1224 + "http-body-util", 1225 + "hyper", 1226 + "hyper-util", 1227 + "native-tls", 1228 + "tokio", 1229 + "tokio-native-tls", 1230 + "tower-service", 1231 + ] 1232 + 1233 + [[package]] 1234 + name = "hyper-util" 1235 + version = "0.1.19" 1236 + source = "registry+https://github.com/rust-lang/crates.io-index" 1237 + checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" 1238 + dependencies = [ 1239 + "base64", 1240 + "bytes", 1241 + "futures-channel", 1242 + "futures-core", 1243 + "futures-util", 1244 + "http", 1245 + "http-body", 1246 + "hyper", 1247 + "ipnet", 1248 + "libc", 1249 + "percent-encoding", 1250 + "pin-project-lite", 1251 + "socket2 0.6.1", 1252 + "system-configuration", 1253 + "tokio", 1254 + "tower-service", 1255 + "tracing", 1256 + "windows-registry", 1257 + ] 1258 + 1259 + [[package]] 1260 + name = "iana-time-zone" 1261 + version = "0.1.64" 1262 + source = "registry+https://github.com/rust-lang/crates.io-index" 1263 + checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" 1264 + dependencies = [ 1265 + "android_system_properties", 1266 + "core-foundation-sys", 1267 + "iana-time-zone-haiku", 1268 + "js-sys", 1269 + "log", 1270 + "wasm-bindgen", 1271 + "windows-core", 1272 + ] 1273 + 1274 + [[package]] 1275 + name = "iana-time-zone-haiku" 1276 + version = "0.1.2" 1277 + source = "registry+https://github.com/rust-lang/crates.io-index" 1278 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1279 + dependencies = [ 1280 + "cc", 1281 + ] 1282 + 1283 + [[package]] 1284 + name = "icu_collections" 1285 + version = "2.1.1" 1286 + source = "registry+https://github.com/rust-lang/crates.io-index" 1287 + checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" 1288 + dependencies = [ 1289 + "displaydoc", 1290 + "potential_utf", 1291 + "yoke", 1292 + "zerofrom", 1293 + "zerovec", 1294 + ] 1295 + 1296 + [[package]] 1297 + name = "icu_locale_core" 1298 + version = "2.1.1" 1299 + source = "registry+https://github.com/rust-lang/crates.io-index" 1300 + checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" 1301 + dependencies = [ 1302 + "displaydoc", 1303 + "litemap", 1304 + "tinystr", 1305 + "writeable", 1306 + "zerovec", 1307 + ] 1308 + 1309 + [[package]] 1310 + name = "icu_normalizer" 1311 + version = "2.1.1" 1312 + source = "registry+https://github.com/rust-lang/crates.io-index" 1313 + checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" 1314 + dependencies = [ 1315 + "icu_collections", 1316 + "icu_normalizer_data", 1317 + "icu_properties", 1318 + "icu_provider", 1319 + "smallvec", 1320 + "zerovec", 1321 + ] 1322 + 1323 + [[package]] 1324 + name = "icu_normalizer_data" 1325 + version = "2.1.1" 1326 + source = "registry+https://github.com/rust-lang/crates.io-index" 1327 + checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" 1328 + 1329 + [[package]] 1330 + name = "icu_properties" 1331 + version = "2.1.2" 1332 + source = "registry+https://github.com/rust-lang/crates.io-index" 1333 + checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" 1334 + dependencies = [ 1335 + "icu_collections", 1336 + "icu_locale_core", 1337 + "icu_properties_data", 1338 + "icu_provider", 1339 + "zerotrie", 1340 + "zerovec", 1341 + ] 1342 + 1343 + [[package]] 1344 + name = "icu_properties_data" 1345 + version = "2.1.2" 1346 + source = "registry+https://github.com/rust-lang/crates.io-index" 1347 + checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" 1348 + 1349 + [[package]] 1350 + name = "icu_provider" 1351 + version = "2.1.1" 1352 + source = "registry+https://github.com/rust-lang/crates.io-index" 1353 + checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" 1354 + dependencies = [ 1355 + "displaydoc", 1356 + "icu_locale_core", 1357 + "writeable", 1358 + "yoke", 1359 + "zerofrom", 1360 + "zerotrie", 1361 + "zerovec", 1362 + ] 1363 + 1364 + [[package]] 1365 + name = "idna" 1366 + version = "1.1.0" 1367 + source = "registry+https://github.com/rust-lang/crates.io-index" 1368 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1369 + dependencies = [ 1370 + "idna_adapter", 1371 + "smallvec", 1372 + "utf8_iter", 1373 + ] 1374 + 1375 + [[package]] 1376 + name = "idna_adapter" 1377 + version = "1.2.1" 1378 + source = "registry+https://github.com/rust-lang/crates.io-index" 1379 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1380 + dependencies = [ 1381 + "icu_normalizer", 1382 + "icu_properties", 1383 + ] 1384 + 1385 + [[package]] 1386 + name = "indexmap" 1387 + version = "2.13.0" 1388 + source = "registry+https://github.com/rust-lang/crates.io-index" 1389 + checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" 1390 + dependencies = [ 1391 + "equivalent", 1392 + "hashbrown 0.16.1", 1393 + ] 1394 + 1395 + [[package]] 1396 + name = "inout" 1397 + version = "0.1.4" 1398 + source = "registry+https://github.com/rust-lang/crates.io-index" 1399 + checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" 1400 + dependencies = [ 1401 + "generic-array", 1402 + ] 1403 + 1404 + [[package]] 1405 + name = "ipconfig" 1406 + version = "0.3.2" 1407 + source = "registry+https://github.com/rust-lang/crates.io-index" 1408 + checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" 1409 + dependencies = [ 1410 + "socket2 0.5.10", 1411 + "widestring", 1412 + "windows-sys 0.48.0", 1413 + "winreg", 1414 + ] 1415 + 1416 + [[package]] 1417 + name = "ipld-core" 1418 + version = "0.4.2" 1419 + source = "registry+https://github.com/rust-lang/crates.io-index" 1420 + checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 1421 + dependencies = [ 1422 + "cid", 1423 + "serde", 1424 + "serde_bytes", 1425 + ] 1426 + 1427 + [[package]] 1428 + name = "ipnet" 1429 + version = "2.11.0" 1430 + source = "registry+https://github.com/rust-lang/crates.io-index" 1431 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1432 + 1433 + [[package]] 1434 + name = "iri-string" 1435 + version = "0.7.10" 1436 + source = "registry+https://github.com/rust-lang/crates.io-index" 1437 + checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" 1438 + dependencies = [ 1439 + "memchr", 1440 + "serde", 1441 + ] 1442 + 1443 + [[package]] 1444 + name = "itoa" 1445 + version = "1.0.17" 1446 + source = "registry+https://github.com/rust-lang/crates.io-index" 1447 + checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" 1448 + 1449 + [[package]] 1450 + name = "js-sys" 1451 + version = "0.3.85" 1452 + source = "registry+https://github.com/rust-lang/crates.io-index" 1453 + checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" 1454 + dependencies = [ 1455 + "once_cell", 1456 + "wasm-bindgen", 1457 + ] 1458 + 1459 + [[package]] 1460 + name = "jsonwebtoken" 1461 + version = "9.3.1" 1462 + source = "registry+https://github.com/rust-lang/crates.io-index" 1463 + checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" 1464 + dependencies = [ 1465 + "base64", 1466 + "js-sys", 1467 + "pem", 1468 + "ring", 1469 + "serde", 1470 + "serde_json", 1471 + "simple_asn1", 1472 + ] 1473 + 1474 + [[package]] 1475 + name = "k256" 1476 + version = "0.13.4" 1477 + source = "registry+https://github.com/rust-lang/crates.io-index" 1478 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 1479 + dependencies = [ 1480 + "cfg-if", 1481 + "ecdsa", 1482 + "elliptic-curve", 1483 + "once_cell", 1484 + "sha2", 1485 + "signature", 1486 + ] 1487 + 1488 + [[package]] 1489 + name = "keccak" 1490 + version = "0.1.5" 1491 + source = "registry+https://github.com/rust-lang/crates.io-index" 1492 + checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" 1493 + dependencies = [ 1494 + "cpufeatures", 1495 + ] 1496 + 1497 + [[package]] 1498 + name = "lazy_static" 1499 + version = "1.5.0" 1500 + source = "registry+https://github.com/rust-lang/crates.io-index" 1501 + checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1502 + 1503 + [[package]] 1504 + name = "libc" 1505 + version = "0.2.180" 1506 + source = "registry+https://github.com/rust-lang/crates.io-index" 1507 + checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" 1508 + 1509 + [[package]] 1510 + name = "linux-raw-sys" 1511 + version = "0.11.0" 1512 + source = "registry+https://github.com/rust-lang/crates.io-index" 1513 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1514 + 1515 + [[package]] 1516 + name = "litemap" 1517 + version = "0.8.1" 1518 + source = "registry+https://github.com/rust-lang/crates.io-index" 1519 + checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" 1520 + 1521 + [[package]] 1522 + name = "lock_api" 1523 + version = "0.4.14" 1524 + source = "registry+https://github.com/rust-lang/crates.io-index" 1525 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 1526 + dependencies = [ 1527 + "scopeguard", 1528 + ] 1529 + 1530 + [[package]] 1531 + name = "log" 1532 + version = "0.4.29" 1533 + source = "registry+https://github.com/rust-lang/crates.io-index" 1534 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 1535 + 1536 + [[package]] 1537 + name = "lru" 1538 + version = "0.12.5" 1539 + source = "registry+https://github.com/rust-lang/crates.io-index" 1540 + checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" 1541 + dependencies = [ 1542 + "hashbrown 0.15.5", 1543 + ] 1544 + 1545 + [[package]] 1546 + name = "lru-slab" 1547 + version = "0.1.2" 1548 + source = "registry+https://github.com/rust-lang/crates.io-index" 1549 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 1550 + 1551 + [[package]] 1552 + name = "magazi" 1553 + version = "0.1.0" 1554 + dependencies = [ 1555 + "anyhow", 1556 + "async-trait", 1557 + "atproto-attestation", 1558 + "atproto-client", 1559 + "atproto-identity", 1560 + "atproto-oauth", 1561 + "atproto-record", 1562 + "axum", 1563 + "axum-extra", 1564 + "base64", 1565 + "chrono", 1566 + "cid", 1567 + "datalogic-rs", 1568 + "jsonwebtoken", 1569 + "mime_guess", 1570 + "mimetype-detector", 1571 + "minijinja", 1572 + "moka", 1573 + "multihash-codetable", 1574 + "p256", 1575 + "reqwest", 1576 + "rust-embed", 1577 + "serde", 1578 + "serde_json", 1579 + "sha2", 1580 + "thiserror 1.0.69", 1581 + "time", 1582 + "tokio", 1583 + "tokio-util", 1584 + "tower", 1585 + "tower-http", 1586 + "tracing", 1587 + "tracing-subscriber", 1588 + "urlencoding", 1589 + "uuid", 1590 + ] 1591 + 1592 + [[package]] 1593 + name = "match-lookup" 1594 + version = "0.1.2" 1595 + source = "registry+https://github.com/rust-lang/crates.io-index" 1596 + checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" 1597 + dependencies = [ 1598 + "proc-macro2", 1599 + "quote", 1600 + "syn", 1601 + ] 1602 + 1603 + [[package]] 1604 + name = "matchers" 1605 + version = "0.2.0" 1606 + source = "registry+https://github.com/rust-lang/crates.io-index" 1607 + checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" 1608 + dependencies = [ 1609 + "regex-automata", 1610 + ] 1611 + 1612 + [[package]] 1613 + name = "matchit" 1614 + version = "0.8.4" 1615 + source = "registry+https://github.com/rust-lang/crates.io-index" 1616 + checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 1617 + 1618 + [[package]] 1619 + name = "memchr" 1620 + version = "2.7.6" 1621 + source = "registry+https://github.com/rust-lang/crates.io-index" 1622 + checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" 1623 + 1624 + [[package]] 1625 + name = "memo-map" 1626 + version = "0.3.3" 1627 + source = "registry+https://github.com/rust-lang/crates.io-index" 1628 + checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" 1629 + 1630 + [[package]] 1631 + name = "mime" 1632 + version = "0.3.17" 1633 + source = "registry+https://github.com/rust-lang/crates.io-index" 1634 + checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1635 + 1636 + [[package]] 1637 + name = "mime_guess" 1638 + version = "2.0.5" 1639 + source = "registry+https://github.com/rust-lang/crates.io-index" 1640 + checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1641 + dependencies = [ 1642 + "mime", 1643 + "unicase", 1644 + ] 1645 + 1646 + [[package]] 1647 + name = "mimetype-detector" 1648 + version = "0.3.4" 1649 + source = "registry+https://github.com/rust-lang/crates.io-index" 1650 + checksum = "b83f5a6e7ff630835b32ab51746126908b2ad30aa8cad2836df2803c418aabb0" 1651 + 1652 + [[package]] 1653 + name = "minijinja" 1654 + version = "2.14.0" 1655 + source = "registry+https://github.com/rust-lang/crates.io-index" 1656 + checksum = "12ea9ac0a51fb5112607099560fdf0f90366ab088a2a9e6e8ae176794e9806aa" 1657 + dependencies = [ 1658 + "memo-map", 1659 + "self_cell", 1660 + "serde", 1661 + ] 1662 + 1663 + [[package]] 1664 + name = "mio" 1665 + version = "1.1.1" 1666 + source = "registry+https://github.com/rust-lang/crates.io-index" 1667 + checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" 1668 + dependencies = [ 1669 + "libc", 1670 + "wasi", 1671 + "windows-sys 0.61.2", 1672 + ] 1673 + 1674 + [[package]] 1675 + name = "moka" 1676 + version = "0.12.12" 1677 + source = "registry+https://github.com/rust-lang/crates.io-index" 1678 + checksum = "a3dec6bd31b08944e08b58fd99373893a6c17054d6f3ea5006cc894f4f4eee2a" 1679 + dependencies = [ 1680 + "async-lock", 1681 + "crossbeam-channel", 1682 + "crossbeam-epoch", 1683 + "crossbeam-utils", 1684 + "equivalent", 1685 + "event-listener", 1686 + "futures-util", 1687 + "parking_lot", 1688 + "portable-atomic", 1689 + "smallvec", 1690 + "tagptr", 1691 + "uuid", 1692 + ] 1693 + 1694 + [[package]] 1695 + name = "multibase" 1696 + version = "0.9.2" 1697 + source = "registry+https://github.com/rust-lang/crates.io-index" 1698 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 1699 + dependencies = [ 1700 + "base-x", 1701 + "base256emoji", 1702 + "data-encoding", 1703 + "data-encoding-macro", 1704 + ] 1705 + 1706 + [[package]] 1707 + name = "multihash" 1708 + version = "0.19.3" 1709 + source = "registry+https://github.com/rust-lang/crates.io-index" 1710 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 1711 + dependencies = [ 1712 + "core2", 1713 + "serde", 1714 + "unsigned-varint", 1715 + ] 1716 + 1717 + [[package]] 1718 + name = "multihash-codetable" 1719 + version = "0.1.4" 1720 + source = "registry+https://github.com/rust-lang/crates.io-index" 1721 + checksum = "67996849749d25f1da9f238e8ace2ece8f9d6bdf3f9750aaf2ae7de3a5cad8ea" 1722 + dependencies = [ 1723 + "blake2b_simd", 1724 + "blake2s_simd", 1725 + "blake3", 1726 + "core2", 1727 + "digest", 1728 + "multihash-derive", 1729 + "ripemd", 1730 + "sha1", 1731 + "sha2", 1732 + "sha3", 1733 + "strobe-rs", 1734 + ] 1735 + 1736 + [[package]] 1737 + name = "multihash-derive" 1738 + version = "0.9.1" 1739 + source = "registry+https://github.com/rust-lang/crates.io-index" 1740 + checksum = "1f1b7edab35d920890b88643a765fc9bd295cf0201f4154dda231bef9b8404eb" 1741 + dependencies = [ 1742 + "core2", 1743 + "multihash", 1744 + "multihash-derive-impl", 1745 + ] 1746 + 1747 + [[package]] 1748 + name = "multihash-derive-impl" 1749 + version = "0.1.2" 1750 + source = "registry+https://github.com/rust-lang/crates.io-index" 1751 + checksum = "e3dc7141bd06405929948754f0628d247f5ca1865be745099205e5086da957cb" 1752 + dependencies = [ 1753 + "proc-macro-crate", 1754 + "proc-macro2", 1755 + "quote", 1756 + "syn", 1757 + "synstructure", 1758 + ] 1759 + 1760 + [[package]] 1761 + name = "native-tls" 1762 + version = "0.2.14" 1763 + source = "registry+https://github.com/rust-lang/crates.io-index" 1764 + checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 1765 + dependencies = [ 1766 + "libc", 1767 + "log", 1768 + "openssl", 1769 + "openssl-probe", 1770 + "openssl-sys", 1771 + "schannel", 1772 + "security-framework", 1773 + "security-framework-sys", 1774 + "tempfile", 1775 + ] 1776 + 1777 + [[package]] 1778 + name = "nu-ansi-term" 1779 + version = "0.50.3" 1780 + source = "registry+https://github.com/rust-lang/crates.io-index" 1781 + checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" 1782 + dependencies = [ 1783 + "windows-sys 0.61.2", 1784 + ] 1785 + 1786 + [[package]] 1787 + name = "num-bigint" 1788 + version = "0.4.6" 1789 + source = "registry+https://github.com/rust-lang/crates.io-index" 1790 + checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1791 + dependencies = [ 1792 + "num-integer", 1793 + "num-traits", 1794 + ] 1795 + 1796 + [[package]] 1797 + name = "num-conv" 1798 + version = "0.1.0" 1799 + source = "registry+https://github.com/rust-lang/crates.io-index" 1800 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1801 + 1802 + [[package]] 1803 + name = "num-integer" 1804 + version = "0.1.46" 1805 + source = "registry+https://github.com/rust-lang/crates.io-index" 1806 + checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1807 + dependencies = [ 1808 + "num-traits", 1809 + ] 1810 + 1811 + [[package]] 1812 + name = "num-traits" 1813 + version = "0.2.19" 1814 + source = "registry+https://github.com/rust-lang/crates.io-index" 1815 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1816 + dependencies = [ 1817 + "autocfg", 1818 + ] 1819 + 1820 + [[package]] 1821 + name = "once_cell" 1822 + version = "1.21.3" 1823 + source = "registry+https://github.com/rust-lang/crates.io-index" 1824 + checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1825 + dependencies = [ 1826 + "critical-section", 1827 + "portable-atomic", 1828 + ] 1829 + 1830 + [[package]] 1831 + name = "opaque-debug" 1832 + version = "0.3.1" 1833 + source = "registry+https://github.com/rust-lang/crates.io-index" 1834 + checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1835 + 1836 + [[package]] 1837 + name = "openssl" 1838 + version = "0.10.75" 1839 + source = "registry+https://github.com/rust-lang/crates.io-index" 1840 + checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" 1841 + dependencies = [ 1842 + "bitflags 2.10.0", 1843 + "cfg-if", 1844 + "foreign-types", 1845 + "libc", 1846 + "once_cell", 1847 + "openssl-macros", 1848 + "openssl-sys", 1849 + ] 1850 + 1851 + [[package]] 1852 + name = "openssl-macros" 1853 + version = "0.1.1" 1854 + source = "registry+https://github.com/rust-lang/crates.io-index" 1855 + checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 1856 + dependencies = [ 1857 + "proc-macro2", 1858 + "quote", 1859 + "syn", 1860 + ] 1861 + 1862 + [[package]] 1863 + name = "openssl-probe" 1864 + version = "0.1.6" 1865 + source = "registry+https://github.com/rust-lang/crates.io-index" 1866 + checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1867 + 1868 + [[package]] 1869 + name = "openssl-sys" 1870 + version = "0.9.111" 1871 + source = "registry+https://github.com/rust-lang/crates.io-index" 1872 + checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" 1873 + dependencies = [ 1874 + "cc", 1875 + "libc", 1876 + "pkg-config", 1877 + "vcpkg", 1878 + ] 1879 + 1880 + [[package]] 1881 + name = "p256" 1882 + version = "0.13.2" 1883 + source = "registry+https://github.com/rust-lang/crates.io-index" 1884 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 1885 + dependencies = [ 1886 + "ecdsa", 1887 + "elliptic-curve", 1888 + "primeorder", 1889 + "serdect", 1890 + "sha2", 1891 + ] 1892 + 1893 + [[package]] 1894 + name = "p384" 1895 + version = "0.13.1" 1896 + source = "registry+https://github.com/rust-lang/crates.io-index" 1897 + checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" 1898 + dependencies = [ 1899 + "ecdsa", 1900 + "elliptic-curve", 1901 + "primeorder", 1902 + "serdect", 1903 + "sha2", 1904 + ] 1905 + 1906 + [[package]] 1907 + name = "parking" 1908 + version = "2.2.1" 1909 + source = "registry+https://github.com/rust-lang/crates.io-index" 1910 + checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" 1911 + 1912 + [[package]] 1913 + name = "parking_lot" 1914 + version = "0.12.5" 1915 + source = "registry+https://github.com/rust-lang/crates.io-index" 1916 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 1917 + dependencies = [ 1918 + "lock_api", 1919 + "parking_lot_core", 1920 + ] 1921 + 1922 + [[package]] 1923 + name = "parking_lot_core" 1924 + version = "0.9.12" 1925 + source = "registry+https://github.com/rust-lang/crates.io-index" 1926 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 1927 + dependencies = [ 1928 + "cfg-if", 1929 + "libc", 1930 + "redox_syscall", 1931 + "smallvec", 1932 + "windows-link", 1933 + ] 1934 + 1935 + [[package]] 1936 + name = "pem" 1937 + version = "3.0.6" 1938 + source = "registry+https://github.com/rust-lang/crates.io-index" 1939 + checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" 1940 + dependencies = [ 1941 + "base64", 1942 + "serde_core", 1943 + ] 1944 + 1945 + [[package]] 1946 + name = "pem-rfc7468" 1947 + version = "0.7.0" 1948 + source = "registry+https://github.com/rust-lang/crates.io-index" 1949 + checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" 1950 + dependencies = [ 1951 + "base64ct", 1952 + ] 1953 + 1954 + [[package]] 1955 + name = "percent-encoding" 1956 + version = "2.3.2" 1957 + source = "registry+https://github.com/rust-lang/crates.io-index" 1958 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1959 + 1960 + [[package]] 1961 + name = "pin-project-lite" 1962 + version = "0.2.16" 1963 + source = "registry+https://github.com/rust-lang/crates.io-index" 1964 + checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1965 + 1966 + [[package]] 1967 + name = "pin-utils" 1968 + version = "0.1.0" 1969 + source = "registry+https://github.com/rust-lang/crates.io-index" 1970 + checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1971 + 1972 + [[package]] 1973 + name = "pkcs8" 1974 + version = "0.10.2" 1975 + source = "registry+https://github.com/rust-lang/crates.io-index" 1976 + checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 1977 + dependencies = [ 1978 + "der", 1979 + "spki", 1980 + ] 1981 + 1982 + [[package]] 1983 + name = "pkg-config" 1984 + version = "0.3.32" 1985 + source = "registry+https://github.com/rust-lang/crates.io-index" 1986 + checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1987 + 1988 + [[package]] 1989 + name = "polyval" 1990 + version = "0.6.2" 1991 + source = "registry+https://github.com/rust-lang/crates.io-index" 1992 + checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" 1993 + dependencies = [ 1994 + "cfg-if", 1995 + "cpufeatures", 1996 + "opaque-debug", 1997 + "universal-hash", 1998 + ] 1999 + 2000 + [[package]] 2001 + name = "portable-atomic" 2002 + version = "1.13.0" 2003 + source = "registry+https://github.com/rust-lang/crates.io-index" 2004 + checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" 2005 + 2006 + [[package]] 2007 + name = "potential_utf" 2008 + version = "0.1.4" 2009 + source = "registry+https://github.com/rust-lang/crates.io-index" 2010 + checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" 2011 + dependencies = [ 2012 + "zerovec", 2013 + ] 2014 + 2015 + [[package]] 2016 + name = "powerfmt" 2017 + version = "0.2.0" 2018 + source = "registry+https://github.com/rust-lang/crates.io-index" 2019 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2020 + 2021 + [[package]] 2022 + name = "ppv-lite86" 2023 + version = "0.2.21" 2024 + source = "registry+https://github.com/rust-lang/crates.io-index" 2025 + checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 2026 + dependencies = [ 2027 + "zerocopy", 2028 + ] 2029 + 2030 + [[package]] 2031 + name = "primeorder" 2032 + version = "0.13.6" 2033 + source = "registry+https://github.com/rust-lang/crates.io-index" 2034 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 2035 + dependencies = [ 2036 + "elliptic-curve", 2037 + "serdect", 2038 + ] 2039 + 2040 + [[package]] 2041 + name = "proc-macro-crate" 2042 + version = "3.4.0" 2043 + source = "registry+https://github.com/rust-lang/crates.io-index" 2044 + checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" 2045 + dependencies = [ 2046 + "toml_edit", 2047 + ] 2048 + 2049 + [[package]] 2050 + name = "proc-macro2" 2051 + version = "1.0.105" 2052 + source = "registry+https://github.com/rust-lang/crates.io-index" 2053 + checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" 2054 + dependencies = [ 2055 + "unicode-ident", 2056 + ] 2057 + 2058 + [[package]] 2059 + name = "quinn" 2060 + version = "0.11.9" 2061 + source = "registry+https://github.com/rust-lang/crates.io-index" 2062 + checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 2063 + dependencies = [ 2064 + "bytes", 2065 + "cfg_aliases", 2066 + "pin-project-lite", 2067 + "quinn-proto", 2068 + "quinn-udp", 2069 + "rustc-hash", 2070 + "rustls", 2071 + "socket2 0.6.1", 2072 + "thiserror 2.0.17", 2073 + "tokio", 2074 + "tracing", 2075 + "web-time", 2076 + ] 2077 + 2078 + [[package]] 2079 + name = "quinn-proto" 2080 + version = "0.11.13" 2081 + source = "registry+https://github.com/rust-lang/crates.io-index" 2082 + checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 2083 + dependencies = [ 2084 + "bytes", 2085 + "getrandom 0.3.4", 2086 + "lru-slab", 2087 + "rand 0.9.2", 2088 + "ring", 2089 + "rustc-hash", 2090 + "rustls", 2091 + "rustls-pki-types", 2092 + "slab", 2093 + "thiserror 2.0.17", 2094 + "tinyvec", 2095 + "tracing", 2096 + "web-time", 2097 + ] 2098 + 2099 + [[package]] 2100 + name = "quinn-udp" 2101 + version = "0.5.14" 2102 + source = "registry+https://github.com/rust-lang/crates.io-index" 2103 + checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 2104 + dependencies = [ 2105 + "cfg_aliases", 2106 + "libc", 2107 + "once_cell", 2108 + "socket2 0.6.1", 2109 + "tracing", 2110 + "windows-sys 0.60.2", 2111 + ] 2112 + 2113 + [[package]] 2114 + name = "quote" 2115 + version = "1.0.43" 2116 + source = "registry+https://github.com/rust-lang/crates.io-index" 2117 + checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" 2118 + dependencies = [ 2119 + "proc-macro2", 2120 + ] 2121 + 2122 + [[package]] 2123 + name = "r-efi" 2124 + version = "5.3.0" 2125 + source = "registry+https://github.com/rust-lang/crates.io-index" 2126 + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" 2127 + 2128 + [[package]] 2129 + name = "rand" 2130 + version = "0.8.5" 2131 + source = "registry+https://github.com/rust-lang/crates.io-index" 2132 + checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 2133 + dependencies = [ 2134 + "libc", 2135 + "rand_chacha 0.3.1", 2136 + "rand_core 0.6.4", 2137 + ] 2138 + 2139 + [[package]] 2140 + name = "rand" 2141 + version = "0.9.2" 2142 + source = "registry+https://github.com/rust-lang/crates.io-index" 2143 + checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" 2144 + dependencies = [ 2145 + "rand_chacha 0.9.0", 2146 + "rand_core 0.9.5", 2147 + ] 2148 + 2149 + [[package]] 2150 + name = "rand_chacha" 2151 + version = "0.3.1" 2152 + source = "registry+https://github.com/rust-lang/crates.io-index" 2153 + checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 2154 + dependencies = [ 2155 + "ppv-lite86", 2156 + "rand_core 0.6.4", 2157 + ] 2158 + 2159 + [[package]] 2160 + name = "rand_chacha" 2161 + version = "0.9.0" 2162 + source = "registry+https://github.com/rust-lang/crates.io-index" 2163 + checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" 2164 + dependencies = [ 2165 + "ppv-lite86", 2166 + "rand_core 0.9.5", 2167 + ] 2168 + 2169 + [[package]] 2170 + name = "rand_core" 2171 + version = "0.6.4" 2172 + source = "registry+https://github.com/rust-lang/crates.io-index" 2173 + checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 2174 + dependencies = [ 2175 + "getrandom 0.2.17", 2176 + ] 2177 + 2178 + [[package]] 2179 + name = "rand_core" 2180 + version = "0.9.5" 2181 + source = "registry+https://github.com/rust-lang/crates.io-index" 2182 + checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" 2183 + dependencies = [ 2184 + "getrandom 0.3.4", 2185 + ] 2186 + 2187 + [[package]] 2188 + name = "redox_syscall" 2189 + version = "0.5.18" 2190 + source = "registry+https://github.com/rust-lang/crates.io-index" 2191 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 2192 + dependencies = [ 2193 + "bitflags 2.10.0", 2194 + ] 2195 + 2196 + [[package]] 2197 + name = "regex" 2198 + version = "1.12.2" 2199 + source = "registry+https://github.com/rust-lang/crates.io-index" 2200 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 2201 + dependencies = [ 2202 + "aho-corasick", 2203 + "memchr", 2204 + "regex-automata", 2205 + "regex-syntax", 2206 + ] 2207 + 2208 + [[package]] 2209 + name = "regex-automata" 2210 + version = "0.4.13" 2211 + source = "registry+https://github.com/rust-lang/crates.io-index" 2212 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 2213 + dependencies = [ 2214 + "aho-corasick", 2215 + "memchr", 2216 + "regex-syntax", 2217 + ] 2218 + 2219 + [[package]] 2220 + name = "regex-syntax" 2221 + version = "0.8.8" 2222 + source = "registry+https://github.com/rust-lang/crates.io-index" 2223 + checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" 2224 + 2225 + [[package]] 2226 + name = "reqwest" 2227 + version = "0.12.28" 2228 + source = "registry+https://github.com/rust-lang/crates.io-index" 2229 + checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" 2230 + dependencies = [ 2231 + "base64", 2232 + "bytes", 2233 + "encoding_rs", 2234 + "futures-core", 2235 + "futures-util", 2236 + "h2", 2237 + "http", 2238 + "http-body", 2239 + "http-body-util", 2240 + "hyper", 2241 + "hyper-rustls", 2242 + "hyper-tls", 2243 + "hyper-util", 2244 + "js-sys", 2245 + "log", 2246 + "mime", 2247 + "mime_guess", 2248 + "native-tls", 2249 + "percent-encoding", 2250 + "pin-project-lite", 2251 + "quinn", 2252 + "rustls", 2253 + "rustls-pki-types", 2254 + "serde", 2255 + "serde_json", 2256 + "serde_urlencoded", 2257 + "sync_wrapper", 2258 + "tokio", 2259 + "tokio-native-tls", 2260 + "tokio-rustls", 2261 + "tower", 2262 + "tower-http", 2263 + "tower-service", 2264 + "url", 2265 + "wasm-bindgen", 2266 + "wasm-bindgen-futures", 2267 + "web-sys", 2268 + "webpki-roots", 2269 + ] 2270 + 2271 + [[package]] 2272 + name = "reqwest-chain" 2273 + version = "1.0.0" 2274 + source = "registry+https://github.com/rust-lang/crates.io-index" 2275 + checksum = "da5c014fb79a8227db44a0433d748107750d2550b7fca55c59a3d7ee7d2ee2b2" 2276 + dependencies = [ 2277 + "anyhow", 2278 + "async-trait", 2279 + "http", 2280 + "reqwest-middleware", 2281 + ] 2282 + 2283 + [[package]] 2284 + name = "reqwest-middleware" 2285 + version = "0.4.2" 2286 + source = "registry+https://github.com/rust-lang/crates.io-index" 2287 + checksum = "57f17d28a6e6acfe1733fe24bcd30774d13bffa4b8a22535b4c8c98423088d4e" 2288 + dependencies = [ 2289 + "anyhow", 2290 + "async-trait", 2291 + "http", 2292 + "reqwest", 2293 + "serde", 2294 + "thiserror 1.0.69", 2295 + "tower-service", 2296 + ] 2297 + 2298 + [[package]] 2299 + name = "resolv-conf" 2300 + version = "0.7.6" 2301 + source = "registry+https://github.com/rust-lang/crates.io-index" 2302 + checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" 2303 + 2304 + [[package]] 2305 + name = "rfc6979" 2306 + version = "0.4.0" 2307 + source = "registry+https://github.com/rust-lang/crates.io-index" 2308 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 2309 + dependencies = [ 2310 + "hmac", 2311 + "subtle", 2312 + ] 2313 + 2314 + [[package]] 2315 + name = "ring" 2316 + version = "0.17.14" 2317 + source = "registry+https://github.com/rust-lang/crates.io-index" 2318 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 2319 + dependencies = [ 2320 + "cc", 2321 + "cfg-if", 2322 + "getrandom 0.2.17", 2323 + "libc", 2324 + "untrusted", 2325 + "windows-sys 0.52.0", 2326 + ] 2327 + 2328 + [[package]] 2329 + name = "ripemd" 2330 + version = "0.1.3" 2331 + source = "registry+https://github.com/rust-lang/crates.io-index" 2332 + checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" 2333 + dependencies = [ 2334 + "digest", 2335 + ] 2336 + 2337 + [[package]] 2338 + name = "rust-embed" 2339 + version = "8.11.0" 2340 + source = "registry+https://github.com/rust-lang/crates.io-index" 2341 + checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" 2342 + dependencies = [ 2343 + "rust-embed-impl", 2344 + "rust-embed-utils", 2345 + "walkdir", 2346 + ] 2347 + 2348 + [[package]] 2349 + name = "rust-embed-impl" 2350 + version = "8.11.0" 2351 + source = "registry+https://github.com/rust-lang/crates.io-index" 2352 + checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" 2353 + dependencies = [ 2354 + "proc-macro2", 2355 + "quote", 2356 + "rust-embed-utils", 2357 + "syn", 2358 + "walkdir", 2359 + ] 2360 + 2361 + [[package]] 2362 + name = "rust-embed-utils" 2363 + version = "8.11.0" 2364 + source = "registry+https://github.com/rust-lang/crates.io-index" 2365 + checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" 2366 + dependencies = [ 2367 + "sha2", 2368 + "walkdir", 2369 + ] 2370 + 2371 + [[package]] 2372 + name = "rustc-hash" 2373 + version = "2.1.1" 2374 + source = "registry+https://github.com/rust-lang/crates.io-index" 2375 + checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 2376 + 2377 + [[package]] 2378 + name = "rustix" 2379 + version = "1.1.3" 2380 + source = "registry+https://github.com/rust-lang/crates.io-index" 2381 + checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" 2382 + dependencies = [ 2383 + "bitflags 2.10.0", 2384 + "errno", 2385 + "libc", 2386 + "linux-raw-sys", 2387 + "windows-sys 0.61.2", 2388 + ] 2389 + 2390 + [[package]] 2391 + name = "rustls" 2392 + version = "0.23.36" 2393 + source = "registry+https://github.com/rust-lang/crates.io-index" 2394 + checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" 2395 + dependencies = [ 2396 + "once_cell", 2397 + "ring", 2398 + "rustls-pki-types", 2399 + "rustls-webpki", 2400 + "subtle", 2401 + "zeroize", 2402 + ] 2403 + 2404 + [[package]] 2405 + name = "rustls-pki-types" 2406 + version = "1.13.2" 2407 + source = "registry+https://github.com/rust-lang/crates.io-index" 2408 + checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" 2409 + dependencies = [ 2410 + "web-time", 2411 + "zeroize", 2412 + ] 2413 + 2414 + [[package]] 2415 + name = "rustls-webpki" 2416 + version = "0.103.8" 2417 + source = "registry+https://github.com/rust-lang/crates.io-index" 2418 + checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" 2419 + dependencies = [ 2420 + "ring", 2421 + "rustls-pki-types", 2422 + "untrusted", 2423 + ] 2424 + 2425 + [[package]] 2426 + name = "rustversion" 2427 + version = "1.0.22" 2428 + source = "registry+https://github.com/rust-lang/crates.io-index" 2429 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 2430 + 2431 + [[package]] 2432 + name = "ryu" 2433 + version = "1.0.22" 2434 + source = "registry+https://github.com/rust-lang/crates.io-index" 2435 + checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" 2436 + 2437 + [[package]] 2438 + name = "same-file" 2439 + version = "1.0.6" 2440 + source = "registry+https://github.com/rust-lang/crates.io-index" 2441 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2442 + dependencies = [ 2443 + "winapi-util", 2444 + ] 2445 + 2446 + [[package]] 2447 + name = "schannel" 2448 + version = "0.1.28" 2449 + source = "registry+https://github.com/rust-lang/crates.io-index" 2450 + checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" 2451 + dependencies = [ 2452 + "windows-sys 0.61.2", 2453 + ] 2454 + 2455 + [[package]] 2456 + name = "scopeguard" 2457 + version = "1.2.0" 2458 + source = "registry+https://github.com/rust-lang/crates.io-index" 2459 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2460 + 2461 + [[package]] 2462 + name = "sec1" 2463 + version = "0.7.3" 2464 + source = "registry+https://github.com/rust-lang/crates.io-index" 2465 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 2466 + dependencies = [ 2467 + "base16ct", 2468 + "der", 2469 + "generic-array", 2470 + "pkcs8", 2471 + "serdect", 2472 + "subtle", 2473 + "zeroize", 2474 + ] 2475 + 2476 + [[package]] 2477 + name = "security-framework" 2478 + version = "2.11.1" 2479 + source = "registry+https://github.com/rust-lang/crates.io-index" 2480 + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 2481 + dependencies = [ 2482 + "bitflags 2.10.0", 2483 + "core-foundation", 2484 + "core-foundation-sys", 2485 + "libc", 2486 + "security-framework-sys", 2487 + ] 2488 + 2489 + [[package]] 2490 + name = "security-framework-sys" 2491 + version = "2.15.0" 2492 + source = "registry+https://github.com/rust-lang/crates.io-index" 2493 + checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" 2494 + dependencies = [ 2495 + "core-foundation-sys", 2496 + "libc", 2497 + ] 2498 + 2499 + [[package]] 2500 + name = "self_cell" 2501 + version = "1.2.2" 2502 + source = "registry+https://github.com/rust-lang/crates.io-index" 2503 + checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89" 2504 + 2505 + [[package]] 2506 + name = "serde" 2507 + version = "1.0.228" 2508 + source = "registry+https://github.com/rust-lang/crates.io-index" 2509 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 2510 + dependencies = [ 2511 + "serde_core", 2512 + "serde_derive", 2513 + ] 2514 + 2515 + [[package]] 2516 + name = "serde_bytes" 2517 + version = "0.11.19" 2518 + source = "registry+https://github.com/rust-lang/crates.io-index" 2519 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 2520 + dependencies = [ 2521 + "serde", 2522 + "serde_core", 2523 + ] 2524 + 2525 + [[package]] 2526 + name = "serde_core" 2527 + version = "1.0.228" 2528 + source = "registry+https://github.com/rust-lang/crates.io-index" 2529 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 2530 + dependencies = [ 2531 + "serde_derive", 2532 + ] 2533 + 2534 + [[package]] 2535 + name = "serde_derive" 2536 + version = "1.0.228" 2537 + source = "registry+https://github.com/rust-lang/crates.io-index" 2538 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 2539 + dependencies = [ 2540 + "proc-macro2", 2541 + "quote", 2542 + "syn", 2543 + ] 2544 + 2545 + [[package]] 2546 + name = "serde_ipld_dagcbor" 2547 + version = "0.6.4" 2548 + source = "registry+https://github.com/rust-lang/crates.io-index" 2549 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 2550 + dependencies = [ 2551 + "cbor4ii", 2552 + "ipld-core", 2553 + "scopeguard", 2554 + "serde", 2555 + ] 2556 + 2557 + [[package]] 2558 + name = "serde_json" 2559 + version = "1.0.149" 2560 + source = "registry+https://github.com/rust-lang/crates.io-index" 2561 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 2562 + dependencies = [ 2563 + "indexmap", 2564 + "itoa", 2565 + "memchr", 2566 + "serde", 2567 + "serde_core", 2568 + "zmij", 2569 + ] 2570 + 2571 + [[package]] 2572 + name = "serde_path_to_error" 2573 + version = "0.1.20" 2574 + source = "registry+https://github.com/rust-lang/crates.io-index" 2575 + checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" 2576 + dependencies = [ 2577 + "itoa", 2578 + "serde", 2579 + "serde_core", 2580 + ] 2581 + 2582 + [[package]] 2583 + name = "serde_urlencoded" 2584 + version = "0.7.1" 2585 + source = "registry+https://github.com/rust-lang/crates.io-index" 2586 + checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2587 + dependencies = [ 2588 + "form_urlencoded", 2589 + "itoa", 2590 + "ryu", 2591 + "serde", 2592 + ] 2593 + 2594 + [[package]] 2595 + name = "serdect" 2596 + version = "0.2.0" 2597 + source = "registry+https://github.com/rust-lang/crates.io-index" 2598 + checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" 2599 + dependencies = [ 2600 + "base16ct", 2601 + "serde", 2602 + ] 2603 + 2604 + [[package]] 2605 + name = "sha1" 2606 + version = "0.10.6" 2607 + source = "registry+https://github.com/rust-lang/crates.io-index" 2608 + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2609 + dependencies = [ 2610 + "cfg-if", 2611 + "cpufeatures", 2612 + "digest", 2613 + ] 2614 + 2615 + [[package]] 2616 + name = "sha2" 2617 + version = "0.10.9" 2618 + source = "registry+https://github.com/rust-lang/crates.io-index" 2619 + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" 2620 + dependencies = [ 2621 + "cfg-if", 2622 + "cpufeatures", 2623 + "digest", 2624 + ] 2625 + 2626 + [[package]] 2627 + name = "sha3" 2628 + version = "0.10.8" 2629 + source = "registry+https://github.com/rust-lang/crates.io-index" 2630 + checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" 2631 + dependencies = [ 2632 + "digest", 2633 + "keccak", 2634 + ] 2635 + 2636 + [[package]] 2637 + name = "sharded-slab" 2638 + version = "0.1.7" 2639 + source = "registry+https://github.com/rust-lang/crates.io-index" 2640 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2641 + dependencies = [ 2642 + "lazy_static", 2643 + ] 2644 + 2645 + [[package]] 2646 + name = "shlex" 2647 + version = "1.3.0" 2648 + source = "registry+https://github.com/rust-lang/crates.io-index" 2649 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2650 + 2651 + [[package]] 2652 + name = "signal-hook-registry" 2653 + version = "1.4.8" 2654 + source = "registry+https://github.com/rust-lang/crates.io-index" 2655 + checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" 2656 + dependencies = [ 2657 + "errno", 2658 + "libc", 2659 + ] 2660 + 2661 + [[package]] 2662 + name = "signature" 2663 + version = "2.2.0" 2664 + source = "registry+https://github.com/rust-lang/crates.io-index" 2665 + checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" 2666 + dependencies = [ 2667 + "digest", 2668 + "rand_core 0.6.4", 2669 + ] 2670 + 2671 + [[package]] 2672 + name = "simple_asn1" 2673 + version = "0.6.3" 2674 + source = "registry+https://github.com/rust-lang/crates.io-index" 2675 + checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" 2676 + dependencies = [ 2677 + "num-bigint", 2678 + "num-traits", 2679 + "thiserror 2.0.17", 2680 + "time", 2681 + ] 2682 + 2683 + [[package]] 2684 + name = "slab" 2685 + version = "0.4.11" 2686 + source = "registry+https://github.com/rust-lang/crates.io-index" 2687 + checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" 2688 + 2689 + [[package]] 2690 + name = "smallvec" 2691 + version = "1.15.1" 2692 + source = "registry+https://github.com/rust-lang/crates.io-index" 2693 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 2694 + 2695 + [[package]] 2696 + name = "socket2" 2697 + version = "0.5.10" 2698 + source = "registry+https://github.com/rust-lang/crates.io-index" 2699 + checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 2700 + dependencies = [ 2701 + "libc", 2702 + "windows-sys 0.52.0", 2703 + ] 2704 + 2705 + [[package]] 2706 + name = "socket2" 2707 + version = "0.6.1" 2708 + source = "registry+https://github.com/rust-lang/crates.io-index" 2709 + checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" 2710 + dependencies = [ 2711 + "libc", 2712 + "windows-sys 0.60.2", 2713 + ] 2714 + 2715 + [[package]] 2716 + name = "spki" 2717 + version = "0.7.3" 2718 + source = "registry+https://github.com/rust-lang/crates.io-index" 2719 + checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 2720 + dependencies = [ 2721 + "base64ct", 2722 + "der", 2723 + ] 2724 + 2725 + [[package]] 2726 + name = "stable_deref_trait" 2727 + version = "1.2.1" 2728 + source = "registry+https://github.com/rust-lang/crates.io-index" 2729 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 2730 + 2731 + [[package]] 2732 + name = "strobe-rs" 2733 + version = "0.10.0" 2734 + source = "registry+https://github.com/rust-lang/crates.io-index" 2735 + checksum = "98fe17535ea31344936cc58d29fec9b500b0452ddc4cc24c429c8a921a0e84e5" 2736 + dependencies = [ 2737 + "bitflags 1.3.2", 2738 + "byteorder", 2739 + "keccak", 2740 + "subtle", 2741 + "zeroize", 2742 + ] 2743 + 2744 + [[package]] 2745 + name = "subtle" 2746 + version = "2.6.1" 2747 + source = "registry+https://github.com/rust-lang/crates.io-index" 2748 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2749 + 2750 + [[package]] 2751 + name = "syn" 2752 + version = "2.0.114" 2753 + source = "registry+https://github.com/rust-lang/crates.io-index" 2754 + checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 2755 + dependencies = [ 2756 + "proc-macro2", 2757 + "quote", 2758 + "unicode-ident", 2759 + ] 2760 + 2761 + [[package]] 2762 + name = "sync_wrapper" 2763 + version = "1.0.2" 2764 + source = "registry+https://github.com/rust-lang/crates.io-index" 2765 + checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 2766 + dependencies = [ 2767 + "futures-core", 2768 + ] 2769 + 2770 + [[package]] 2771 + name = "synstructure" 2772 + version = "0.13.2" 2773 + source = "registry+https://github.com/rust-lang/crates.io-index" 2774 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 2775 + dependencies = [ 2776 + "proc-macro2", 2777 + "quote", 2778 + "syn", 2779 + ] 2780 + 2781 + [[package]] 2782 + name = "system-configuration" 2783 + version = "0.6.1" 2784 + source = "registry+https://github.com/rust-lang/crates.io-index" 2785 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2786 + dependencies = [ 2787 + "bitflags 2.10.0", 2788 + "core-foundation", 2789 + "system-configuration-sys", 2790 + ] 2791 + 2792 + [[package]] 2793 + name = "system-configuration-sys" 2794 + version = "0.6.0" 2795 + source = "registry+https://github.com/rust-lang/crates.io-index" 2796 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2797 + dependencies = [ 2798 + "core-foundation-sys", 2799 + "libc", 2800 + ] 2801 + 2802 + [[package]] 2803 + name = "tagptr" 2804 + version = "0.2.0" 2805 + source = "registry+https://github.com/rust-lang/crates.io-index" 2806 + checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 2807 + 2808 + [[package]] 2809 + name = "tempfile" 2810 + version = "3.24.0" 2811 + source = "registry+https://github.com/rust-lang/crates.io-index" 2812 + checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" 2813 + dependencies = [ 2814 + "fastrand", 2815 + "getrandom 0.3.4", 2816 + "once_cell", 2817 + "rustix", 2818 + "windows-sys 0.61.2", 2819 + ] 2820 + 2821 + [[package]] 2822 + name = "thiserror" 2823 + version = "1.0.69" 2824 + source = "registry+https://github.com/rust-lang/crates.io-index" 2825 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2826 + dependencies = [ 2827 + "thiserror-impl 1.0.69", 2828 + ] 2829 + 2830 + [[package]] 2831 + name = "thiserror" 2832 + version = "2.0.17" 2833 + source = "registry+https://github.com/rust-lang/crates.io-index" 2834 + checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" 2835 + dependencies = [ 2836 + "thiserror-impl 2.0.17", 2837 + ] 2838 + 2839 + [[package]] 2840 + name = "thiserror-impl" 2841 + version = "1.0.69" 2842 + source = "registry+https://github.com/rust-lang/crates.io-index" 2843 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2844 + dependencies = [ 2845 + "proc-macro2", 2846 + "quote", 2847 + "syn", 2848 + ] 2849 + 2850 + [[package]] 2851 + name = "thiserror-impl" 2852 + version = "2.0.17" 2853 + source = "registry+https://github.com/rust-lang/crates.io-index" 2854 + checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" 2855 + dependencies = [ 2856 + "proc-macro2", 2857 + "quote", 2858 + "syn", 2859 + ] 2860 + 2861 + [[package]] 2862 + name = "thread_local" 2863 + version = "1.1.9" 2864 + source = "registry+https://github.com/rust-lang/crates.io-index" 2865 + checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" 2866 + dependencies = [ 2867 + "cfg-if", 2868 + ] 2869 + 2870 + [[package]] 2871 + name = "time" 2872 + version = "0.3.45" 2873 + source = "registry+https://github.com/rust-lang/crates.io-index" 2874 + checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" 2875 + dependencies = [ 2876 + "deranged", 2877 + "itoa", 2878 + "num-conv", 2879 + "powerfmt", 2880 + "serde_core", 2881 + "time-core", 2882 + "time-macros", 2883 + ] 2884 + 2885 + [[package]] 2886 + name = "time-core" 2887 + version = "0.1.7" 2888 + source = "registry+https://github.com/rust-lang/crates.io-index" 2889 + checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" 2890 + 2891 + [[package]] 2892 + name = "time-macros" 2893 + version = "0.2.25" 2894 + source = "registry+https://github.com/rust-lang/crates.io-index" 2895 + checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" 2896 + dependencies = [ 2897 + "num-conv", 2898 + "time-core", 2899 + ] 2900 + 2901 + [[package]] 2902 + name = "tinystr" 2903 + version = "0.8.2" 2904 + source = "registry+https://github.com/rust-lang/crates.io-index" 2905 + checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" 2906 + dependencies = [ 2907 + "displaydoc", 2908 + "zerovec", 2909 + ] 2910 + 2911 + [[package]] 2912 + name = "tinyvec" 2913 + version = "1.10.0" 2914 + source = "registry+https://github.com/rust-lang/crates.io-index" 2915 + checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" 2916 + dependencies = [ 2917 + "tinyvec_macros", 2918 + ] 2919 + 2920 + [[package]] 2921 + name = "tinyvec_macros" 2922 + version = "0.1.1" 2923 + source = "registry+https://github.com/rust-lang/crates.io-index" 2924 + checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 2925 + 2926 + [[package]] 2927 + name = "tokio" 2928 + version = "1.49.0" 2929 + source = "registry+https://github.com/rust-lang/crates.io-index" 2930 + checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" 2931 + dependencies = [ 2932 + "bytes", 2933 + "libc", 2934 + "mio", 2935 + "parking_lot", 2936 + "pin-project-lite", 2937 + "signal-hook-registry", 2938 + "socket2 0.6.1", 2939 + "tokio-macros", 2940 + "windows-sys 0.61.2", 2941 + ] 2942 + 2943 + [[package]] 2944 + name = "tokio-macros" 2945 + version = "2.6.0" 2946 + source = "registry+https://github.com/rust-lang/crates.io-index" 2947 + checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" 2948 + dependencies = [ 2949 + "proc-macro2", 2950 + "quote", 2951 + "syn", 2952 + ] 2953 + 2954 + [[package]] 2955 + name = "tokio-native-tls" 2956 + version = "0.3.1" 2957 + source = "registry+https://github.com/rust-lang/crates.io-index" 2958 + checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 2959 + dependencies = [ 2960 + "native-tls", 2961 + "tokio", 2962 + ] 2963 + 2964 + [[package]] 2965 + name = "tokio-rustls" 2966 + version = "0.26.4" 2967 + source = "registry+https://github.com/rust-lang/crates.io-index" 2968 + checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" 2969 + dependencies = [ 2970 + "rustls", 2971 + "tokio", 2972 + ] 2973 + 2974 + [[package]] 2975 + name = "tokio-util" 2976 + version = "0.7.18" 2977 + source = "registry+https://github.com/rust-lang/crates.io-index" 2978 + checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" 2979 + dependencies = [ 2980 + "bytes", 2981 + "futures-core", 2982 + "futures-sink", 2983 + "pin-project-lite", 2984 + "tokio", 2985 + ] 2986 + 2987 + [[package]] 2988 + name = "toml_datetime" 2989 + version = "0.7.5+spec-1.1.0" 2990 + source = "registry+https://github.com/rust-lang/crates.io-index" 2991 + checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" 2992 + dependencies = [ 2993 + "serde_core", 2994 + ] 2995 + 2996 + [[package]] 2997 + name = "toml_edit" 2998 + version = "0.23.10+spec-1.0.0" 2999 + source = "registry+https://github.com/rust-lang/crates.io-index" 3000 + checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" 3001 + dependencies = [ 3002 + "indexmap", 3003 + "toml_datetime", 3004 + "toml_parser", 3005 + "winnow", 3006 + ] 3007 + 3008 + [[package]] 3009 + name = "toml_parser" 3010 + version = "1.0.6+spec-1.1.0" 3011 + source = "registry+https://github.com/rust-lang/crates.io-index" 3012 + checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" 3013 + dependencies = [ 3014 + "winnow", 3015 + ] 3016 + 3017 + [[package]] 3018 + name = "tower" 3019 + version = "0.5.3" 3020 + source = "registry+https://github.com/rust-lang/crates.io-index" 3021 + checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" 3022 + dependencies = [ 3023 + "futures-core", 3024 + "futures-util", 3025 + "pin-project-lite", 3026 + "sync_wrapper", 3027 + "tokio", 3028 + "tower-layer", 3029 + "tower-service", 3030 + "tracing", 3031 + ] 3032 + 3033 + [[package]] 3034 + name = "tower-http" 3035 + version = "0.6.8" 3036 + source = "registry+https://github.com/rust-lang/crates.io-index" 3037 + checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" 3038 + dependencies = [ 3039 + "bitflags 2.10.0", 3040 + "bytes", 3041 + "futures-core", 3042 + "futures-util", 3043 + "http", 3044 + "http-body", 3045 + "http-body-util", 3046 + "http-range-header", 3047 + "httpdate", 3048 + "iri-string", 3049 + "mime", 3050 + "mime_guess", 3051 + "percent-encoding", 3052 + "pin-project-lite", 3053 + "tokio", 3054 + "tokio-util", 3055 + "tower", 3056 + "tower-layer", 3057 + "tower-service", 3058 + "tracing", 3059 + ] 3060 + 3061 + [[package]] 3062 + name = "tower-layer" 3063 + version = "0.3.3" 3064 + source = "registry+https://github.com/rust-lang/crates.io-index" 3065 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 3066 + 3067 + [[package]] 3068 + name = "tower-service" 3069 + version = "0.3.3" 3070 + source = "registry+https://github.com/rust-lang/crates.io-index" 3071 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 3072 + 3073 + [[package]] 3074 + name = "tracing" 3075 + version = "0.1.44" 3076 + source = "registry+https://github.com/rust-lang/crates.io-index" 3077 + checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" 3078 + dependencies = [ 3079 + "log", 3080 + "pin-project-lite", 3081 + "tracing-attributes", 3082 + "tracing-core", 3083 + ] 3084 + 3085 + [[package]] 3086 + name = "tracing-attributes" 3087 + version = "0.1.31" 3088 + source = "registry+https://github.com/rust-lang/crates.io-index" 3089 + checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" 3090 + dependencies = [ 3091 + "proc-macro2", 3092 + "quote", 3093 + "syn", 3094 + ] 3095 + 3096 + [[package]] 3097 + name = "tracing-core" 3098 + version = "0.1.36" 3099 + source = "registry+https://github.com/rust-lang/crates.io-index" 3100 + checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" 3101 + dependencies = [ 3102 + "once_cell", 3103 + "valuable", 3104 + ] 3105 + 3106 + [[package]] 3107 + name = "tracing-log" 3108 + version = "0.2.0" 3109 + source = "registry+https://github.com/rust-lang/crates.io-index" 3110 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 3111 + dependencies = [ 3112 + "log", 3113 + "once_cell", 3114 + "tracing-core", 3115 + ] 3116 + 3117 + [[package]] 3118 + name = "tracing-subscriber" 3119 + version = "0.3.22" 3120 + source = "registry+https://github.com/rust-lang/crates.io-index" 3121 + checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" 3122 + dependencies = [ 3123 + "matchers", 3124 + "nu-ansi-term", 3125 + "once_cell", 3126 + "regex-automata", 3127 + "sharded-slab", 3128 + "smallvec", 3129 + "thread_local", 3130 + "tracing", 3131 + "tracing-core", 3132 + "tracing-log", 3133 + ] 3134 + 3135 + [[package]] 3136 + name = "try-lock" 3137 + version = "0.2.5" 3138 + source = "registry+https://github.com/rust-lang/crates.io-index" 3139 + checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 3140 + 3141 + [[package]] 3142 + name = "typenum" 3143 + version = "1.19.0" 3144 + source = "registry+https://github.com/rust-lang/crates.io-index" 3145 + checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" 3146 + 3147 + [[package]] 3148 + name = "ulid" 3149 + version = "1.2.1" 3150 + source = "registry+https://github.com/rust-lang/crates.io-index" 3151 + checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe" 3152 + dependencies = [ 3153 + "rand 0.9.2", 3154 + "web-time", 3155 + ] 3156 + 3157 + [[package]] 3158 + name = "unicase" 3159 + version = "2.9.0" 3160 + source = "registry+https://github.com/rust-lang/crates.io-index" 3161 + checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" 3162 + 3163 + [[package]] 3164 + name = "unicode-ident" 3165 + version = "1.0.22" 3166 + source = "registry+https://github.com/rust-lang/crates.io-index" 3167 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 3168 + 3169 + [[package]] 3170 + name = "universal-hash" 3171 + version = "0.5.1" 3172 + source = "registry+https://github.com/rust-lang/crates.io-index" 3173 + checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 3174 + dependencies = [ 3175 + "crypto-common", 3176 + "subtle", 3177 + ] 3178 + 3179 + [[package]] 3180 + name = "unsigned-varint" 3181 + version = "0.8.0" 3182 + source = "registry+https://github.com/rust-lang/crates.io-index" 3183 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 3184 + 3185 + [[package]] 3186 + name = "untrusted" 3187 + version = "0.9.0" 3188 + source = "registry+https://github.com/rust-lang/crates.io-index" 3189 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 3190 + 3191 + [[package]] 3192 + name = "url" 3193 + version = "2.5.8" 3194 + source = "registry+https://github.com/rust-lang/crates.io-index" 3195 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 3196 + dependencies = [ 3197 + "form_urlencoded", 3198 + "idna", 3199 + "percent-encoding", 3200 + "serde", 3201 + ] 3202 + 3203 + [[package]] 3204 + name = "urlencoding" 3205 + version = "2.1.3" 3206 + source = "registry+https://github.com/rust-lang/crates.io-index" 3207 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 3208 + 3209 + [[package]] 3210 + name = "utf8_iter" 3211 + version = "1.0.4" 3212 + source = "registry+https://github.com/rust-lang/crates.io-index" 3213 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 3214 + 3215 + [[package]] 3216 + name = "uuid" 3217 + version = "1.19.0" 3218 + source = "registry+https://github.com/rust-lang/crates.io-index" 3219 + checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" 3220 + dependencies = [ 3221 + "getrandom 0.3.4", 3222 + "js-sys", 3223 + "wasm-bindgen", 3224 + ] 3225 + 3226 + [[package]] 3227 + name = "valuable" 3228 + version = "0.1.1" 3229 + source = "registry+https://github.com/rust-lang/crates.io-index" 3230 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 3231 + 3232 + [[package]] 3233 + name = "vcpkg" 3234 + version = "0.2.15" 3235 + source = "registry+https://github.com/rust-lang/crates.io-index" 3236 + checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 3237 + 3238 + [[package]] 3239 + name = "version_check" 3240 + version = "0.9.5" 3241 + source = "registry+https://github.com/rust-lang/crates.io-index" 3242 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 3243 + 3244 + [[package]] 3245 + name = "walkdir" 3246 + version = "2.5.0" 3247 + source = "registry+https://github.com/rust-lang/crates.io-index" 3248 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 3249 + dependencies = [ 3250 + "same-file", 3251 + "winapi-util", 3252 + ] 3253 + 3254 + [[package]] 3255 + name = "want" 3256 + version = "0.3.1" 3257 + source = "registry+https://github.com/rust-lang/crates.io-index" 3258 + checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 3259 + dependencies = [ 3260 + "try-lock", 3261 + ] 3262 + 3263 + [[package]] 3264 + name = "wasi" 3265 + version = "0.11.1+wasi-snapshot-preview1" 3266 + source = "registry+https://github.com/rust-lang/crates.io-index" 3267 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3268 + 3269 + [[package]] 3270 + name = "wasip2" 3271 + version = "1.0.2+wasi-0.2.9" 3272 + source = "registry+https://github.com/rust-lang/crates.io-index" 3273 + checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" 3274 + dependencies = [ 3275 + "wit-bindgen", 3276 + ] 3277 + 3278 + [[package]] 3279 + name = "wasm-bindgen" 3280 + version = "0.2.108" 3281 + source = "registry+https://github.com/rust-lang/crates.io-index" 3282 + checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" 3283 + dependencies = [ 3284 + "cfg-if", 3285 + "once_cell", 3286 + "rustversion", 3287 + "wasm-bindgen-macro", 3288 + "wasm-bindgen-shared", 3289 + ] 3290 + 3291 + [[package]] 3292 + name = "wasm-bindgen-futures" 3293 + version = "0.4.58" 3294 + source = "registry+https://github.com/rust-lang/crates.io-index" 3295 + checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" 3296 + dependencies = [ 3297 + "cfg-if", 3298 + "futures-util", 3299 + "js-sys", 3300 + "once_cell", 3301 + "wasm-bindgen", 3302 + "web-sys", 3303 + ] 3304 + 3305 + [[package]] 3306 + name = "wasm-bindgen-macro" 3307 + version = "0.2.108" 3308 + source = "registry+https://github.com/rust-lang/crates.io-index" 3309 + checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" 3310 + dependencies = [ 3311 + "quote", 3312 + "wasm-bindgen-macro-support", 3313 + ] 3314 + 3315 + [[package]] 3316 + name = "wasm-bindgen-macro-support" 3317 + version = "0.2.108" 3318 + source = "registry+https://github.com/rust-lang/crates.io-index" 3319 + checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" 3320 + dependencies = [ 3321 + "bumpalo", 3322 + "proc-macro2", 3323 + "quote", 3324 + "syn", 3325 + "wasm-bindgen-shared", 3326 + ] 3327 + 3328 + [[package]] 3329 + name = "wasm-bindgen-shared" 3330 + version = "0.2.108" 3331 + source = "registry+https://github.com/rust-lang/crates.io-index" 3332 + checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" 3333 + dependencies = [ 3334 + "unicode-ident", 3335 + ] 3336 + 3337 + [[package]] 3338 + name = "web-sys" 3339 + version = "0.3.85" 3340 + source = "registry+https://github.com/rust-lang/crates.io-index" 3341 + checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" 3342 + dependencies = [ 3343 + "js-sys", 3344 + "wasm-bindgen", 3345 + ] 3346 + 3347 + [[package]] 3348 + name = "web-time" 3349 + version = "1.1.0" 3350 + source = "registry+https://github.com/rust-lang/crates.io-index" 3351 + checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 3352 + dependencies = [ 3353 + "js-sys", 3354 + "wasm-bindgen", 3355 + ] 3356 + 3357 + [[package]] 3358 + name = "webpki-roots" 3359 + version = "1.0.5" 3360 + source = "registry+https://github.com/rust-lang/crates.io-index" 3361 + checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" 3362 + dependencies = [ 3363 + "rustls-pki-types", 3364 + ] 3365 + 3366 + [[package]] 3367 + name = "widestring" 3368 + version = "1.2.1" 3369 + source = "registry+https://github.com/rust-lang/crates.io-index" 3370 + checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" 3371 + 3372 + [[package]] 3373 + name = "winapi-util" 3374 + version = "0.1.11" 3375 + source = "registry+https://github.com/rust-lang/crates.io-index" 3376 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 3377 + dependencies = [ 3378 + "windows-sys 0.61.2", 3379 + ] 3380 + 3381 + [[package]] 3382 + name = "windows-core" 3383 + version = "0.62.2" 3384 + source = "registry+https://github.com/rust-lang/crates.io-index" 3385 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 3386 + dependencies = [ 3387 + "windows-implement", 3388 + "windows-interface", 3389 + "windows-link", 3390 + "windows-result", 3391 + "windows-strings", 3392 + ] 3393 + 3394 + [[package]] 3395 + name = "windows-implement" 3396 + version = "0.60.2" 3397 + source = "registry+https://github.com/rust-lang/crates.io-index" 3398 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 3399 + dependencies = [ 3400 + "proc-macro2", 3401 + "quote", 3402 + "syn", 3403 + ] 3404 + 3405 + [[package]] 3406 + name = "windows-interface" 3407 + version = "0.59.3" 3408 + source = "registry+https://github.com/rust-lang/crates.io-index" 3409 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 3410 + dependencies = [ 3411 + "proc-macro2", 3412 + "quote", 3413 + "syn", 3414 + ] 3415 + 3416 + [[package]] 3417 + name = "windows-link" 3418 + version = "0.2.1" 3419 + source = "registry+https://github.com/rust-lang/crates.io-index" 3420 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 3421 + 3422 + [[package]] 3423 + name = "windows-registry" 3424 + version = "0.6.1" 3425 + source = "registry+https://github.com/rust-lang/crates.io-index" 3426 + checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" 3427 + dependencies = [ 3428 + "windows-link", 3429 + "windows-result", 3430 + "windows-strings", 3431 + ] 3432 + 3433 + [[package]] 3434 + name = "windows-result" 3435 + version = "0.4.1" 3436 + source = "registry+https://github.com/rust-lang/crates.io-index" 3437 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 3438 + dependencies = [ 3439 + "windows-link", 3440 + ] 3441 + 3442 + [[package]] 3443 + name = "windows-strings" 3444 + version = "0.5.1" 3445 + source = "registry+https://github.com/rust-lang/crates.io-index" 3446 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 3447 + dependencies = [ 3448 + "windows-link", 3449 + ] 3450 + 3451 + [[package]] 3452 + name = "windows-sys" 3453 + version = "0.48.0" 3454 + source = "registry+https://github.com/rust-lang/crates.io-index" 3455 + checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3456 + dependencies = [ 3457 + "windows-targets 0.48.5", 3458 + ] 3459 + 3460 + [[package]] 3461 + name = "windows-sys" 3462 + version = "0.52.0" 3463 + source = "registry+https://github.com/rust-lang/crates.io-index" 3464 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3465 + dependencies = [ 3466 + "windows-targets 0.52.6", 3467 + ] 3468 + 3469 + [[package]] 3470 + name = "windows-sys" 3471 + version = "0.60.2" 3472 + source = "registry+https://github.com/rust-lang/crates.io-index" 3473 + checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" 3474 + dependencies = [ 3475 + "windows-targets 0.53.5", 3476 + ] 3477 + 3478 + [[package]] 3479 + name = "windows-sys" 3480 + version = "0.61.2" 3481 + source = "registry+https://github.com/rust-lang/crates.io-index" 3482 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 3483 + dependencies = [ 3484 + "windows-link", 3485 + ] 3486 + 3487 + [[package]] 3488 + name = "windows-targets" 3489 + version = "0.48.5" 3490 + source = "registry+https://github.com/rust-lang/crates.io-index" 3491 + checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3492 + dependencies = [ 3493 + "windows_aarch64_gnullvm 0.48.5", 3494 + "windows_aarch64_msvc 0.48.5", 3495 + "windows_i686_gnu 0.48.5", 3496 + "windows_i686_msvc 0.48.5", 3497 + "windows_x86_64_gnu 0.48.5", 3498 + "windows_x86_64_gnullvm 0.48.5", 3499 + "windows_x86_64_msvc 0.48.5", 3500 + ] 3501 + 3502 + [[package]] 3503 + name = "windows-targets" 3504 + version = "0.52.6" 3505 + source = "registry+https://github.com/rust-lang/crates.io-index" 3506 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3507 + dependencies = [ 3508 + "windows_aarch64_gnullvm 0.52.6", 3509 + "windows_aarch64_msvc 0.52.6", 3510 + "windows_i686_gnu 0.52.6", 3511 + "windows_i686_gnullvm 0.52.6", 3512 + "windows_i686_msvc 0.52.6", 3513 + "windows_x86_64_gnu 0.52.6", 3514 + "windows_x86_64_gnullvm 0.52.6", 3515 + "windows_x86_64_msvc 0.52.6", 3516 + ] 3517 + 3518 + [[package]] 3519 + name = "windows-targets" 3520 + version = "0.53.5" 3521 + source = "registry+https://github.com/rust-lang/crates.io-index" 3522 + checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" 3523 + dependencies = [ 3524 + "windows-link", 3525 + "windows_aarch64_gnullvm 0.53.1", 3526 + "windows_aarch64_msvc 0.53.1", 3527 + "windows_i686_gnu 0.53.1", 3528 + "windows_i686_gnullvm 0.53.1", 3529 + "windows_i686_msvc 0.53.1", 3530 + "windows_x86_64_gnu 0.53.1", 3531 + "windows_x86_64_gnullvm 0.53.1", 3532 + "windows_x86_64_msvc 0.53.1", 3533 + ] 3534 + 3535 + [[package]] 3536 + name = "windows_aarch64_gnullvm" 3537 + version = "0.48.5" 3538 + source = "registry+https://github.com/rust-lang/crates.io-index" 3539 + checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3540 + 3541 + [[package]] 3542 + name = "windows_aarch64_gnullvm" 3543 + version = "0.52.6" 3544 + source = "registry+https://github.com/rust-lang/crates.io-index" 3545 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3546 + 3547 + [[package]] 3548 + name = "windows_aarch64_gnullvm" 3549 + version = "0.53.1" 3550 + source = "registry+https://github.com/rust-lang/crates.io-index" 3551 + checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" 3552 + 3553 + [[package]] 3554 + name = "windows_aarch64_msvc" 3555 + version = "0.48.5" 3556 + source = "registry+https://github.com/rust-lang/crates.io-index" 3557 + checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3558 + 3559 + [[package]] 3560 + name = "windows_aarch64_msvc" 3561 + version = "0.52.6" 3562 + source = "registry+https://github.com/rust-lang/crates.io-index" 3563 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3564 + 3565 + [[package]] 3566 + name = "windows_aarch64_msvc" 3567 + version = "0.53.1" 3568 + source = "registry+https://github.com/rust-lang/crates.io-index" 3569 + checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" 3570 + 3571 + [[package]] 3572 + name = "windows_i686_gnu" 3573 + version = "0.48.5" 3574 + source = "registry+https://github.com/rust-lang/crates.io-index" 3575 + checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3576 + 3577 + [[package]] 3578 + name = "windows_i686_gnu" 3579 + version = "0.52.6" 3580 + source = "registry+https://github.com/rust-lang/crates.io-index" 3581 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3582 + 3583 + [[package]] 3584 + name = "windows_i686_gnu" 3585 + version = "0.53.1" 3586 + source = "registry+https://github.com/rust-lang/crates.io-index" 3587 + checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" 3588 + 3589 + [[package]] 3590 + name = "windows_i686_gnullvm" 3591 + version = "0.52.6" 3592 + source = "registry+https://github.com/rust-lang/crates.io-index" 3593 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3594 + 3595 + [[package]] 3596 + name = "windows_i686_gnullvm" 3597 + version = "0.53.1" 3598 + source = "registry+https://github.com/rust-lang/crates.io-index" 3599 + checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" 3600 + 3601 + [[package]] 3602 + name = "windows_i686_msvc" 3603 + version = "0.48.5" 3604 + source = "registry+https://github.com/rust-lang/crates.io-index" 3605 + checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3606 + 3607 + [[package]] 3608 + name = "windows_i686_msvc" 3609 + version = "0.52.6" 3610 + source = "registry+https://github.com/rust-lang/crates.io-index" 3611 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3612 + 3613 + [[package]] 3614 + name = "windows_i686_msvc" 3615 + version = "0.53.1" 3616 + source = "registry+https://github.com/rust-lang/crates.io-index" 3617 + checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" 3618 + 3619 + [[package]] 3620 + name = "windows_x86_64_gnu" 3621 + version = "0.48.5" 3622 + source = "registry+https://github.com/rust-lang/crates.io-index" 3623 + checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3624 + 3625 + [[package]] 3626 + name = "windows_x86_64_gnu" 3627 + version = "0.52.6" 3628 + source = "registry+https://github.com/rust-lang/crates.io-index" 3629 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 3630 + 3631 + [[package]] 3632 + name = "windows_x86_64_gnu" 3633 + version = "0.53.1" 3634 + source = "registry+https://github.com/rust-lang/crates.io-index" 3635 + checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" 3636 + 3637 + [[package]] 3638 + name = "windows_x86_64_gnullvm" 3639 + version = "0.48.5" 3640 + source = "registry+https://github.com/rust-lang/crates.io-index" 3641 + checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3642 + 3643 + [[package]] 3644 + name = "windows_x86_64_gnullvm" 3645 + version = "0.52.6" 3646 + source = "registry+https://github.com/rust-lang/crates.io-index" 3647 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 3648 + 3649 + [[package]] 3650 + name = "windows_x86_64_gnullvm" 3651 + version = "0.53.1" 3652 + source = "registry+https://github.com/rust-lang/crates.io-index" 3653 + checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" 3654 + 3655 + [[package]] 3656 + name = "windows_x86_64_msvc" 3657 + version = "0.48.5" 3658 + source = "registry+https://github.com/rust-lang/crates.io-index" 3659 + checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 3660 + 3661 + [[package]] 3662 + name = "windows_x86_64_msvc" 3663 + version = "0.52.6" 3664 + source = "registry+https://github.com/rust-lang/crates.io-index" 3665 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3666 + 3667 + [[package]] 3668 + name = "windows_x86_64_msvc" 3669 + version = "0.53.1" 3670 + source = "registry+https://github.com/rust-lang/crates.io-index" 3671 + checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" 3672 + 3673 + [[package]] 3674 + name = "winnow" 3675 + version = "0.7.14" 3676 + source = "registry+https://github.com/rust-lang/crates.io-index" 3677 + checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" 3678 + dependencies = [ 3679 + "memchr", 3680 + ] 3681 + 3682 + [[package]] 3683 + name = "winreg" 3684 + version = "0.50.0" 3685 + source = "registry+https://github.com/rust-lang/crates.io-index" 3686 + checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 3687 + dependencies = [ 3688 + "cfg-if", 3689 + "windows-sys 0.48.0", 3690 + ] 3691 + 3692 + [[package]] 3693 + name = "wit-bindgen" 3694 + version = "0.51.0" 3695 + source = "registry+https://github.com/rust-lang/crates.io-index" 3696 + checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 3697 + 3698 + [[package]] 3699 + name = "writeable" 3700 + version = "0.6.2" 3701 + source = "registry+https://github.com/rust-lang/crates.io-index" 3702 + checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" 3703 + 3704 + [[package]] 3705 + name = "yoke" 3706 + version = "0.8.1" 3707 + source = "registry+https://github.com/rust-lang/crates.io-index" 3708 + checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" 3709 + dependencies = [ 3710 + "stable_deref_trait", 3711 + "yoke-derive", 3712 + "zerofrom", 3713 + ] 3714 + 3715 + [[package]] 3716 + name = "yoke-derive" 3717 + version = "0.8.1" 3718 + source = "registry+https://github.com/rust-lang/crates.io-index" 3719 + checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" 3720 + dependencies = [ 3721 + "proc-macro2", 3722 + "quote", 3723 + "syn", 3724 + "synstructure", 3725 + ] 3726 + 3727 + [[package]] 3728 + name = "zerocopy" 3729 + version = "0.8.33" 3730 + source = "registry+https://github.com/rust-lang/crates.io-index" 3731 + checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" 3732 + dependencies = [ 3733 + "zerocopy-derive", 3734 + ] 3735 + 3736 + [[package]] 3737 + name = "zerocopy-derive" 3738 + version = "0.8.33" 3739 + source = "registry+https://github.com/rust-lang/crates.io-index" 3740 + checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" 3741 + dependencies = [ 3742 + "proc-macro2", 3743 + "quote", 3744 + "syn", 3745 + ] 3746 + 3747 + [[package]] 3748 + name = "zerofrom" 3749 + version = "0.1.6" 3750 + source = "registry+https://github.com/rust-lang/crates.io-index" 3751 + checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 3752 + dependencies = [ 3753 + "zerofrom-derive", 3754 + ] 3755 + 3756 + [[package]] 3757 + name = "zerofrom-derive" 3758 + version = "0.1.6" 3759 + source = "registry+https://github.com/rust-lang/crates.io-index" 3760 + checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 3761 + dependencies = [ 3762 + "proc-macro2", 3763 + "quote", 3764 + "syn", 3765 + "synstructure", 3766 + ] 3767 + 3768 + [[package]] 3769 + name = "zeroize" 3770 + version = "1.8.2" 3771 + source = "registry+https://github.com/rust-lang/crates.io-index" 3772 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 3773 + dependencies = [ 3774 + "zeroize_derive", 3775 + ] 3776 + 3777 + [[package]] 3778 + name = "zeroize_derive" 3779 + version = "1.4.3" 3780 + source = "registry+https://github.com/rust-lang/crates.io-index" 3781 + checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" 3782 + dependencies = [ 3783 + "proc-macro2", 3784 + "quote", 3785 + "syn", 3786 + ] 3787 + 3788 + [[package]] 3789 + name = "zerotrie" 3790 + version = "0.2.3" 3791 + source = "registry+https://github.com/rust-lang/crates.io-index" 3792 + checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" 3793 + dependencies = [ 3794 + "displaydoc", 3795 + "yoke", 3796 + "zerofrom", 3797 + ] 3798 + 3799 + [[package]] 3800 + name = "zerovec" 3801 + version = "0.11.5" 3802 + source = "registry+https://github.com/rust-lang/crates.io-index" 3803 + checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" 3804 + dependencies = [ 3805 + "yoke", 3806 + "zerofrom", 3807 + "zerovec-derive", 3808 + ] 3809 + 3810 + [[package]] 3811 + name = "zerovec-derive" 3812 + version = "0.11.2" 3813 + source = "registry+https://github.com/rust-lang/crates.io-index" 3814 + checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" 3815 + dependencies = [ 3816 + "proc-macro2", 3817 + "quote", 3818 + "syn", 3819 + ] 3820 + 3821 + [[package]] 3822 + name = "zmij" 3823 + version = "1.0.14" 3824 + source = "registry+https://github.com/rust-lang/crates.io-index" 3825 + checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea"
+80
Cargo.toml
··· 1 + [package] 2 + name = "magazi" 3 + version = "0.1.0" 4 + edition = "2024" 5 + 6 + [dependencies] 7 + # Web framework 8 + axum = { version = "0.8", features = ["macros"] } 9 + axum-extra = { version = "0.10", features = ["cookie-signed", "cookie-private", "typed-header"] } 10 + tower = "0.5" 11 + tower-http = { version = "0.6", features = ["fs", "trace", "set-header", "limit"] } 12 + 13 + # Async runtime 14 + tokio = { version = "1", features = ["full"] } 15 + 16 + # ATProtocol crates 17 + atproto-client = { git = "https://tangled.sh/@smokesignal.events/atproto-identity-rs" } 18 + atproto-oauth = { git = "https://tangled.sh/@smokesignal.events/atproto-identity-rs" } 19 + atproto-attestation = { git = "https://tangled.sh/@smokesignal.events/atproto-identity-rs" } 20 + atproto-identity = { git = "https://tangled.sh/@smokesignal.events/atproto-identity-rs" } 21 + atproto-record = { git = "https://tangled.sh/@smokesignal.events/atproto-identity-rs" } 22 + 23 + # HTTP client 24 + reqwest = { version = "0.12", features = ["json"] } 25 + 26 + # Serialization 27 + serde = { version = "1", features = ["derive"] } 28 + serde_json = "1" 29 + 30 + # Templating 31 + minijinja = { version = "2", features = ["loader"] } 32 + rust-embed = "8" 33 + 34 + # JWT handling 35 + jsonwebtoken = "9" 36 + 37 + # Cryptography 38 + sha2 = "0.10" 39 + base64 = "0.22" 40 + p256 = { version = "0.13", features = ["ecdsa", "pkcs8", "pem"] } 41 + 42 + # URL encoding 43 + urlencoding = "2" 44 + 45 + # MIME type guessing 46 + mime_guess = "2" 47 + mimetype-detector = "0.3" 48 + 49 + # Streaming file reads 50 + tokio-util = { version = "0.7", features = ["io"] } 51 + 52 + # Time for cookies 53 + time = "0.3" 54 + 55 + # Time 56 + chrono = { version = "0.4", features = ["serde"] } 57 + 58 + # Async traits 59 + async-trait = "0.1" 60 + 61 + # Error handling 62 + anyhow = "1" 63 + thiserror = "1" 64 + 65 + # Logging 66 + tracing = "0.1" 67 + tracing-subscriber = { version = "0.3", features = ["env-filter"] } 68 + 69 + # UUID for nonces 70 + uuid = { version = "1", features = ["v4"] } 71 + 72 + # Caching 73 + moka = { version = "0.12", features = ["future"] } 74 + 75 + # CID generation 76 + cid = "0.11" 77 + multihash-codetable = { version = "0.1", features = ["sha2"] } 78 + 79 + # JSONLogic rule evaluation 80 + datalogic-rs = "4"
+44
Dockerfile
··· 1 + # syntax=docker/dockerfile:1.4 2 + FROM rust:1.92-slim-bookworm AS builder 3 + 4 + RUN apt-get update && apt-get install -y \ 5 + pkg-config \ 6 + libssl-dev \ 7 + && rm -rf /var/lib/apt/lists/* 8 + 9 + WORKDIR /app 10 + COPY Cargo.toml build.rs ./ 11 + 12 + ARG TEMPLATES=./templates 13 + ARG GIT_HASH=0 14 + ENV GIT_HASH=$GIT_HASH 15 + ENV TEMPLATES_PATH=$TEMPLATES 16 + 17 + COPY src ./src 18 + COPY ${TEMPLATES} ./templates 19 + COPY files ./files 20 + 21 + RUN cargo build --release --bin magazi 22 + 23 + FROM gcr.io/distroless/cc-debian12 24 + 25 + LABEL org.opencontainers.image.title="Magazi" 26 + LABEL org.opencontainers.image.description="A digital content store powered by ATProtocol OAuth." 27 + LABEL org.opencontainers.image.licenses="MIT" 28 + LABEL org.opencontainers.image.authors="Nick Gerakines <nick.gerakines@gmail.com>" 29 + LABEL org.opencontainers.image.source="https://tangled.sh/@ngerakines.me/magazi" 30 + LABEL org.opencontainers.image.version="0.1.0" 31 + 32 + WORKDIR /app 33 + COPY --from=builder /app/target/release/magazi /app/magazi 34 + COPY --from=builder /app/files ./files 35 + 36 + ENV HTTP_PORT=8080 37 + ENV FILES=/app/files 38 + ENV CATALOG=/app/files/catalog.json 39 + ENV RUST_LOG=info 40 + ENV RUST_BACKTRACE=full 41 + 42 + EXPOSE 8080 43 + 44 + ENTRYPOINT ["/app/magazi"]
+6
build.rs
··· 1 + //! Build script for cargo rerun triggers. 2 + 3 + fn main() { 4 + // Rerun if templates change 5 + println!("cargo:rerun-if-changed=templates"); 6 + }
+32
files/catalog.json
··· 1 + [ 2 + { 3 + "id": "bafkreif2gmzhha3eiaznudhp5lthedtbo3bsgzksrymbeoisfun5rwbyqi", 4 + "name": "Resume", 5 + "description": "This is my resume as of October 2025.", 6 + "content_type": "application/pdf", 7 + "icon": "fa-solid fa-briefcase", 8 + "requirements": {} 9 + }, 10 + { 11 + "id": "bafkreiciye5m6bpyv6gvenub7f75eo7ifouheiy4evsk6pklwrtpsmaiqi", 12 + "name": "Hello from me and gadget", 13 + "description": "A picture with a very cute person, and then me. You don't need to be a supporter to download this digital content, just logged in.", 14 + "content_type": "image/jpeg", 15 + "icon": "fa-regular fa-file-image", 16 + "requirements": { 17 + "==": [ 18 + { 19 + "var": "authenticated" 20 + }, 21 + true 22 + ] 23 + } 24 + }, 25 + { 26 + "id": "bafkreib7xwfunygo4nuav24wjzqdyeo3734eovvgzkwcpui2uw4gkj2cwm", 27 + "name": "ATProtocol OAuth Implementation Agent Skill", 28 + "description": "The atprotocol-oauth skill is a comprehensive prompt that can be used to fully implement ATProtocol OAuth in your backend web application. Additionally, it can be used to validate an implementation to ensure it is up to spec.", 29 + "content_type": "text/markdown", 30 + "icon": "fa-brands fa-markdown" 31 + } 32 + ]
+118
src/bin/catalog-manager.rs
··· 1 + //! Catalog manager binary for adding entries to the catalog. 2 + //! 3 + //! Usage: catalog-manager <name> <file-path> <description> [icon] 4 + //! 5 + //! Creates or updates entries in ./files/catalog.json with the format: 6 + //! {"id": "[raw CID of file]", "name": "[name]", "description": "[description]", "content_type": "[detected]", "icon": "[optional]"} 7 + //! 8 + //! The content_type is automatically detected from the file contents using mimetype-detector. 9 + //! The icon is an optional FontAwesome class (e.g., "fa-solid fa-book"). 10 + //! 11 + //! Also copies the file to ./files/[cid] 12 + 13 + use std::env; 14 + use std::fs; 15 + use std::path::Path; 16 + 17 + use cid::Cid; 18 + use multihash_codetable::{Code, MultihashDigest}; 19 + use serde::{Deserialize, Serialize}; 20 + 21 + /// Raw codec for CID (0x55) 22 + const RAW_CODEC: u64 = 0x55; 23 + 24 + #[derive(Debug, Clone, Serialize, Deserialize)] 25 + struct CatalogEntry { 26 + id: String, 27 + name: String, 28 + description: String, 29 + #[serde(skip_serializing_if = "Option::is_none")] 30 + content_type: Option<String>, 31 + #[serde(skip_serializing_if = "Option::is_none")] 32 + icon: Option<String>, 33 + } 34 + 35 + fn compute_raw_cid(data: &[u8]) -> String { 36 + let hash = Code::Sha2_256.digest(data); 37 + let cid = Cid::new_v1(RAW_CODEC, hash); 38 + cid.to_string() 39 + } 40 + 41 + fn main() -> anyhow::Result<()> { 42 + let args: Vec<String> = env::args().collect(); 43 + 44 + if args.len() < 4 || args.len() > 5 { 45 + eprintln!("Usage: {} <name> <file-path> <description> [icon]", args[0]); 46 + eprintln!(); 47 + eprintln!("Arguments:"); 48 + eprintln!(" name Name of the catalog entry"); 49 + eprintln!(" file-path Path to the file to add"); 50 + eprintln!(" description Description of the catalog entry"); 51 + eprintln!(" icon Optional FontAwesome icon class (e.g., \"fa-solid fa-book\")"); 52 + eprintln!(); 53 + eprintln!("The content type is automatically detected from the file contents."); 54 + std::process::exit(1); 55 + } 56 + 57 + let name = &args[1]; 58 + let file_path = &args[2]; 59 + let description = &args[3]; 60 + let icon = args.get(4).cloned(); 61 + 62 + let file_path = Path::new(file_path); 63 + if !file_path.exists() { 64 + eprintln!("Error: File not found: {}", file_path.display()); 65 + std::process::exit(1); 66 + } 67 + 68 + let file_contents = fs::read(file_path)?; 69 + let cid = compute_raw_cid(&file_contents); 70 + 71 + // Detect MIME type from file contents 72 + let detected_mime = mimetype_detector::detect(&file_contents); 73 + let content_type = detected_mime.mime().to_string(); 74 + println!("Detected content type: {}", content_type); 75 + 76 + // Ensure ./files/ directory exists and copy file 77 + let files_dir = Path::new("./files"); 78 + fs::create_dir_all(files_dir)?; 79 + 80 + let dest_path = files_dir.join(&cid); 81 + fs::write(&dest_path, &file_contents)?; 82 + println!("Copied file to {}", dest_path.display()); 83 + 84 + let new_entry = CatalogEntry { 85 + id: cid.clone(), 86 + name: name.clone(), 87 + description: description.clone(), 88 + content_type: Some(content_type), 89 + icon: icon.clone(), 90 + }; 91 + 92 + let catalog_path = files_dir.join("catalog.json"); 93 + 94 + let mut catalog: Vec<CatalogEntry> = if catalog_path.exists() { 95 + let contents = fs::read_to_string(&catalog_path)?; 96 + serde_json::from_str(&contents).unwrap_or_default() 97 + } else { 98 + Vec::new() 99 + }; 100 + 101 + if let Some(existing) = catalog.iter_mut().find(|e| e.id == cid) { 102 + existing.name = new_entry.name; 103 + existing.description = new_entry.description; 104 + existing.content_type = new_entry.content_type; 105 + existing.icon = new_entry.icon; 106 + println!("Updated existing entry with CID: {}", cid); 107 + } else { 108 + catalog.push(new_entry); 109 + println!("Added new entry with CID: {}", cid); 110 + } 111 + 112 + let json = serde_json::to_string_pretty(&catalog)?; 113 + fs::write(&catalog_path, json)?; 114 + 115 + println!("Catalog saved to {}", catalog_path.display()); 116 + 117 + Ok(()) 118 + }
+104
src/config.rs
··· 1 + use std::fs; 2 + use std::path::PathBuf; 3 + 4 + use anyhow::{Context, Result}; 5 + use atproto_identity::key::{KeyData, identify_key}; 6 + use serde::{Deserialize, Serialize}; 7 + 8 + use crate::entitlements::default_requirements; 9 + 10 + /// A catalog entry from catalog.json (without download URL). 11 + #[derive(Clone, Debug, Serialize, Deserialize)] 12 + pub struct CatalogEntry { 13 + pub id: String, 14 + pub name: String, 15 + pub description: String, 16 + /// Optional content type override for download and getBlob responses. 17 + /// When set, this value is used instead of mime_guess inference. 18 + #[serde(skip_serializing_if = "Option::is_none")] 19 + pub content_type: Option<String>, 20 + /// Optional FontAwesome icon class (e.g., "fa-solid fa-book"). 21 + /// When not set, defaults to "fa-regular fa-file". 22 + #[serde(skip_serializing_if = "Option::is_none")] 23 + pub icon: Option<String>, 24 + /// JSONLogic rule for determining entitlement to this catalog item. 25 + /// When not set, uses the default requirements (authenticated + supporter proof + broker proof). 26 + #[serde(default = "default_requirements")] 27 + pub requirements: serde_json::Value, 28 + } 29 + 30 + #[derive(Clone)] 31 + pub struct Config { 32 + pub http_port: u16, 33 + pub http_external: String, 34 + pub cookie_key: Vec<u8>, 35 + pub oauth_client_credentials: KeyData, 36 + pub creator_identity: String, 37 + pub broker_identity: String, 38 + pub download_key: KeyData, 39 + pub files_path: PathBuf, 40 + pub catalog: Vec<CatalogEntry>, 41 + } 42 + 43 + impl Config { 44 + pub fn from_env() -> Result<Self> { 45 + let http_port: u16 = std::env::var("HTTP_PORT") 46 + .context("HTTP_PORT not set")? 47 + .parse() 48 + .context("HTTP_PORT must be a valid port number")?; 49 + 50 + let http_external = std::env::var("HTTP_EXTERNAL").context("HTTP_EXTERNAL not set")?; 51 + 52 + let cookie_key_b64 = std::env::var("HTTP_COOKIE_KEY").context("HTTP_COOKIE_KEY not set")?; 53 + let cookie_key = 54 + base64::Engine::decode(&base64::engine::general_purpose::STANDARD, &cookie_key_b64) 55 + .context("HTTP_COOKIE_KEY must be valid base64")?; 56 + 57 + let oauth_client_credentials_str = std::env::var("OAUTH_CLIENT_CREDENTIALS") 58 + .context("OAUTH_CLIENT_CREDENTIALS not set")?; 59 + let oauth_client_credentials = identify_key(&oauth_client_credentials_str) 60 + .map_err(|e| anyhow::anyhow!("Invalid OAUTH_CLIENT_CREDENTIALS: {}", e))?; 61 + 62 + let creator_identity = 63 + std::env::var("CREATOR_IDENTITY").context("CREATOR_IDENTITY not set")?; 64 + 65 + let broker_identity = 66 + std::env::var("BROKER_IDENTITY").context("BROKER_IDENTITY not set")?; 67 + 68 + let download_key_str = std::env::var("DOWNLOAD_KEY").context("DOWNLOAD_KEY not set")?; 69 + let download_key = identify_key(&download_key_str) 70 + .map_err(|e| anyhow::anyhow!("Invalid DOWNLOAD_KEY: {}", e))?; 71 + 72 + let files_path = PathBuf::from(std::env::var("FILES").context("FILES not set")?); 73 + 74 + let catalog_path = PathBuf::from(std::env::var("CATALOG").context("CATALOG not set")?); 75 + let catalog_contents = fs::read_to_string(&catalog_path) 76 + .with_context(|| format!("Failed to read catalog file: {}", catalog_path.display()))?; 77 + let catalog: Vec<CatalogEntry> = serde_json::from_str(&catalog_contents) 78 + .with_context(|| format!("Failed to parse catalog file: {}", catalog_path.display()))?; 79 + 80 + Ok(Config { 81 + http_port, 82 + http_external, 83 + cookie_key, 84 + oauth_client_credentials, 85 + creator_identity, 86 + broker_identity, 87 + download_key, 88 + files_path, 89 + catalog, 90 + }) 91 + } 92 + 93 + pub fn client_id(&self) -> String { 94 + format!("https://{}/oauth-client-metadata.json", self.http_external) 95 + } 96 + 97 + pub fn redirect_uri(&self) -> String { 98 + format!("https://{}/login/callback", self.http_external) 99 + } 100 + 101 + pub fn external_url(&self) -> String { 102 + format!("https://{}", self.http_external) 103 + } 104 + }
+96
src/entitlements.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + /// Context for evaluating entitlement rules. 4 + /// 5 + /// This struct is serialized to JSON and passed to JSONLogic rule evaluation. 6 + /// Each field becomes a variable that can be referenced in rules using `{"var": "fieldName"}`. 7 + /// 8 + /// Note: All fields are always serialized (including null values) to ensure 9 + /// JSONLogic comparisons work correctly. 10 + #[derive(Debug, Clone, Serialize, Deserialize)] 11 + #[serde(rename_all = "camelCase")] 12 + pub struct EntitlementContext { 13 + /// Whether the user is authenticated 14 + pub authenticated: bool, 15 + 16 + /// The user's DID (if authenticated) 17 + pub did: Option<String>, 18 + 19 + /// The user's handle (if authenticated) 20 + pub handle: Option<String>, 21 + 22 + /// Full supporter record from the user's PDS 23 + pub supporter: Option<serde_json::Value>, 24 + 25 + /// Full proof record from the creator (fetched via StrongRef) 26 + pub supporter_proof: Option<serde_json::Value>, 27 + 28 + /// Full proof record from the broker (fetched via StrongRef) 29 + pub broker_proof: Option<serde_json::Value>, 30 + } 31 + 32 + impl EntitlementContext { 33 + /// Create an unauthenticated context 34 + pub fn unauthenticated() -> Self { 35 + Self { 36 + authenticated: false, 37 + did: None, 38 + handle: None, 39 + supporter: None, 40 + supporter_proof: None, 41 + broker_proof: None, 42 + } 43 + } 44 + 45 + /// Create an authenticated context without supporter records 46 + /// 47 + /// This is used for logged-in users who don't have any supporter records 48 + /// but should still be able to access content that only requires authentication. 49 + pub fn authenticated(did: String, handle: Option<String>) -> Self { 50 + Self { 51 + authenticated: true, 52 + did: Some(did), 53 + handle, 54 + supporter: None, 55 + supporter_proof: None, 56 + broker_proof: None, 57 + } 58 + } 59 + 60 + /// Create a full context with supporter records 61 + pub fn with_supporter( 62 + did: String, 63 + handle: Option<String>, 64 + supporter: serde_json::Value, 65 + supporter_proof: serde_json::Value, 66 + broker_proof: serde_json::Value, 67 + ) -> Self { 68 + Self { 69 + authenticated: true, 70 + did: Some(did), 71 + handle, 72 + supporter: Some(supporter), 73 + supporter_proof: Some(supporter_proof), 74 + broker_proof: Some(broker_proof), 75 + } 76 + } 77 + } 78 + 79 + /// Returns the default requirements rule for catalog entries. 80 + /// 81 + /// This rule requires: 82 + /// - User must be authenticated 83 + /// - User must have a valid supporterProof from the creator 84 + /// - User must have a valid brokerProof from the broker 85 + /// 86 + /// Uses the `!!` (double-bang) operator to check for truthiness, which is 87 + /// safer than null comparisons across different JSONLogic implementations. 88 + pub fn default_requirements() -> serde_json::Value { 89 + serde_json::json!({ 90 + "and": [ 91 + {"==": [{"var": "authenticated"}, true]}, 92 + {"!!": [{"var": "supporterProof"}]}, 93 + {"!!": [{"var": "brokerProof"}]} 94 + ] 95 + }) 96 + }
+29
src/error.rs
··· 1 + use axum::{ 2 + http::StatusCode, 3 + response::{IntoResponse, Response}, 4 + }; 5 + use thiserror::Error; 6 + 7 + #[derive(Error, Debug)] 8 + pub enum AppError { 9 + #[error("OAuth error: {0}")] 10 + OAuth(String), 11 + 12 + #[error("Identity resolution error: {0}")] 13 + Identity(String), 14 + 15 + #[error("Internal error: {0}")] 16 + Internal(#[from] anyhow::Error), 17 + } 18 + 19 + impl IntoResponse for AppError { 20 + fn into_response(self) -> Response { 21 + let status = match &self { 22 + AppError::OAuth(_) => StatusCode::BAD_REQUEST, 23 + AppError::Identity(_) => StatusCode::BAD_REQUEST, 24 + AppError::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR, 25 + }; 26 + 27 + (status, self.to_string()).into_response() 28 + } 29 + }
+356
src/handlers/handler_auth.rs
··· 1 + use std::sync::Arc; 2 + 3 + use atproto_identity::key::{KeyType, generate_key}; 4 + use atproto_oauth::errors::OAuthClientError; 5 + use atproto_oauth::pkce; 6 + use atproto_oauth::resources::{ 7 + AuthorizationServer, oauth_authorization_server, oauth_protected_resource, pds_resources, 8 + }; 9 + use atproto_oauth::storage::OAuthRequestStorage; 10 + use atproto_oauth::workflow::{OAuthRequest, OAuthRequestState, oauth_complete, oauth_init}; 11 + use axum::{ 12 + Form, 13 + extract::{Query, State}, 14 + response::{Html, IntoResponse, Redirect, Response}, 15 + }; 16 + use axum_extra::extract::cookie::PrivateCookieJar; 17 + use chrono::{Duration, Utc}; 18 + use minijinja::context; 19 + use serde::Deserialize; 20 + use uuid::Uuid; 21 + 22 + use crate::error::AppError; 23 + use crate::session::{Session, SessionJar}; 24 + use crate::state::AppState; 25 + 26 + pub async fn login_form(State(state): State<Arc<AppState>>, jar: SessionJar) -> Html<String> { 27 + let session = jar.get_session(); 28 + let logged_in = session.is_some(); 29 + 30 + // Resolve creator's handle from their DID 31 + let creator_did = &state.config.creator_identity; 32 + let creator_handle = state 33 + .identity_resolver 34 + .resolve(creator_did) 35 + .await 36 + .ok() 37 + .and_then(|doc| doc.also_known_as.first().cloned()) 38 + .and_then(|aka| aka.strip_prefix("at://").map(|s| s.to_string())) 39 + .unwrap_or_else(|| creator_did.clone()); 40 + 41 + let template = state.templates.get_template("login.html").unwrap(); 42 + let rendered = template 43 + .render(context! { 44 + logged_in => logged_in, 45 + creator_did => creator_did, 46 + creator_handle => creator_handle, 47 + error => None::<String>, 48 + }) 49 + .unwrap_or_else(|e| { 50 + tracing::error!("Template render error: {}", e); 51 + "An error occurred while loading the page.".to_string() 52 + }); 53 + 54 + Html(rendered) 55 + } 56 + 57 + #[derive(Deserialize)] 58 + pub struct LoginForm { 59 + handle: String, 60 + } 61 + 62 + pub async fn protected_resource_or_authorization( 63 + http_client: &reqwest::Client, 64 + url: &str, 65 + ) -> Result<AuthorizationServer, anyhow::Error> { 66 + let authorization_server_url = 67 + if let Ok(protected_resource) = oauth_protected_resource(http_client, url).await { 68 + protected_resource 69 + .authorization_servers 70 + .first() 71 + .cloned() 72 + .ok_or(OAuthClientError::InvalidOAuthProtectedResource)? 73 + } else { 74 + url.to_string() 75 + }; 76 + 77 + let authorization_server = 78 + oauth_authorization_server(http_client, &authorization_server_url).await?; 79 + Ok(authorization_server) 80 + } 81 + 82 + pub async fn login_start( 83 + State(state): State<Arc<AppState>>, 84 + Form(form): Form<LoginForm>, 85 + ) -> Result<Redirect, Html<String>> { 86 + // Clean up expired OAuth requests to prevent memory leaks 87 + let _ = state.oauth_storage.clear_expired_oauth_requests().await; 88 + 89 + let input = form.handle.trim(); 90 + // Strip common handle prefixes (@ or at://) 91 + let input = input.strip_prefix('@').unwrap_or(input); 92 + let input = input.strip_prefix("at://").unwrap_or(input); 93 + 94 + let result = 95 + async { 96 + // Determine if input is a PDS URL or a handle/DID 97 + let (auth_server, login_hint) = 98 + if input.starts_with("https://") || input.starts_with("http://") { 99 + // Direct PDS URL - no identity resolution needed 100 + let authorization_server = 101 + protected_resource_or_authorization(&state.http_client, input).await?; 102 + (authorization_server, None) 103 + } else { 104 + // Handle or DID - resolve via identity resolver 105 + let doc = state.identity_resolver.resolve(input).await.map_err(|_| { 106 + AppError::Identity("Failed to resolve identity".to_string()) 107 + })?; 108 + 109 + let pds_endpoints = doc.pds_endpoints(); 110 + let pds_url = pds_endpoints.first().ok_or_else(|| { 111 + AppError::Identity("No PDS endpoint found for identity".to_string()) 112 + })?; 113 + 114 + let (_, auth_server) = pds_resources(&state.http_client, &pds_url) 115 + .await 116 + .map_err(|_| { 117 + AppError::OAuth("Failed to discover authorization server".to_string()) 118 + })?; 119 + 120 + (auth_server, Some(input.to_string())) 121 + }; 122 + 123 + let (pkce_verifier, code_challenge) = pkce::generate(); 124 + 125 + let oauth_state = Uuid::new_v4().to_string(); 126 + let nonce = Uuid::new_v4().to_string(); 127 + 128 + let dpop_key = 129 + generate_key(KeyType::P256Private).map_err(|e| AppError::Internal(e.into()))?; 130 + 131 + let oauth_request_state = OAuthRequestState { 132 + state: oauth_state.clone(), 133 + nonce: nonce.clone(), 134 + code_challenge, 135 + scope: "atproto".to_string(), 136 + }; 137 + 138 + let par_response = oauth_init( 139 + &state.http_client, 140 + &state.oauth_client, 141 + &dpop_key, 142 + login_hint.as_deref(), 143 + &auth_server, 144 + &oauth_request_state, 145 + ) 146 + .await 147 + .map_err(|_| AppError::OAuth("Failed to initiate authorization".to_string()))?; 148 + 149 + let oauth_request = OAuthRequest { 150 + oauth_state: oauth_state.clone(), 151 + issuer: auth_server.issuer.clone(), 152 + authorization_server: auth_server.issuer.clone(), 153 + nonce, 154 + pkce_verifier, 155 + signing_public_key: state.config.client_id(), 156 + dpop_private_key: dpop_key.to_string(), 157 + created_at: Utc::now(), 158 + expires_at: Utc::now() + Duration::minutes(15), 159 + }; 160 + 161 + state 162 + .oauth_storage 163 + .insert_oauth_request(oauth_request) 164 + .await?; 165 + 166 + let auth_url = format!( 167 + "{}?client_id={}&request_uri={}", 168 + auth_server.authorization_endpoint, 169 + urlencoding::encode(&state.config.client_id()), 170 + urlencoding::encode(&par_response.request_uri) 171 + ); 172 + 173 + Ok::<_, AppError>(Redirect::to(&auth_url)) 174 + } 175 + .await; 176 + 177 + match result { 178 + Ok(redirect) => Ok(redirect), 179 + Err(e) => { 180 + // Log the actual error for debugging 181 + tracing::warn!("Login start failed: {}", e); 182 + 183 + // Provide generic error messages to users 184 + let user_error = match e { 185 + AppError::Identity(_) => "Unable to find or verify the provided identity", 186 + AppError::OAuth(_) => "Unable to start the authorization process", 187 + _ => "An unexpected error occurred during login", 188 + }; 189 + 190 + // Resolve creator's handle for template 191 + let creator_did = &state.config.creator_identity; 192 + let creator_handle = state 193 + .identity_resolver 194 + .resolve(creator_did) 195 + .await 196 + .ok() 197 + .and_then(|doc| doc.also_known_as.first().cloned()) 198 + .and_then(|aka| aka.strip_prefix("at://").map(|s| s.to_string())) 199 + .unwrap_or_else(|| creator_did.clone()); 200 + 201 + let template = state.templates.get_template("login.html").unwrap(); 202 + let rendered = template 203 + .render(context! { 204 + logged_in => false, 205 + creator_did => creator_did, 206 + creator_handle => creator_handle, 207 + error => Some(user_error), 208 + }) 209 + .unwrap_or_else(|e| { 210 + tracing::error!("Template render error: {}", e); 211 + "An error occurred while loading the page.".to_string() 212 + }); 213 + Err(Html(rendered)) 214 + } 215 + } 216 + } 217 + 218 + #[derive(Deserialize)] 219 + pub struct CallbackQuery { 220 + state: Option<String>, 221 + code: Option<String>, 222 + iss: Option<String>, 223 + error: Option<String>, 224 + #[allow(dead_code)] 225 + error_description: Option<String>, 226 + } 227 + 228 + pub async fn login_callback( 229 + State(state): State<Arc<AppState>>, 230 + Query(query): Query<CallbackQuery>, 231 + jar: SessionJar, 232 + ) -> Response { 233 + let result = async { 234 + if query.error.is_some() { 235 + return Err(AppError::OAuth("Authorization was denied".to_string())); 236 + } 237 + 238 + let oauth_state = query 239 + .state 240 + .as_ref() 241 + .ok_or_else(|| AppError::OAuth("Invalid callback parameters".to_string()))?; 242 + 243 + let code = query 244 + .code 245 + .as_ref() 246 + .ok_or_else(|| AppError::OAuth("Invalid callback parameters".to_string()))?; 247 + 248 + let oauth_request = state 249 + .oauth_storage 250 + .get_oauth_request_by_state(oauth_state) 251 + .await? 252 + .ok_or_else(|| AppError::OAuth("Login session expired or invalid".to_string()))?; 253 + 254 + if let Some(iss) = &query.iss { 255 + if iss != &oauth_request.issuer { 256 + return Err(AppError::OAuth("Authorization server mismatch".to_string())); 257 + } 258 + } 259 + 260 + if oauth_request.expires_at < Utc::now() { 261 + state 262 + .oauth_storage 263 + .delete_oauth_request_by_state(oauth_state) 264 + .await?; 265 + return Err(AppError::OAuth("Login session expired".to_string())); 266 + } 267 + 268 + let dpop_key = atproto_identity::key::identify_key(&oauth_request.dpop_private_key) 269 + .map_err(|_| AppError::Internal(anyhow::anyhow!("Key processing error")))?; 270 + 271 + let auth_server = oauth_authorization_server(&state.http_client, &oauth_request.issuer) 272 + .await 273 + .map_err(|_| AppError::OAuth("Failed to contact authorization server".to_string()))?; 274 + 275 + let token_response = oauth_complete( 276 + &state.http_client, 277 + &state.oauth_client, 278 + &dpop_key, 279 + code, 280 + &oauth_request, 281 + &auth_server, 282 + ) 283 + .await 284 + .map_err(|_| AppError::OAuth("Failed to complete authorization".to_string()))?; 285 + 286 + state 287 + .oauth_storage 288 + .delete_oauth_request_by_state(oauth_state) 289 + .await?; 290 + 291 + let did = token_response 292 + .sub 293 + .ok_or_else(|| AppError::OAuth("Invalid token response".to_string()))?; 294 + 295 + let document = state.identity_resolver.resolve(&did).await?; 296 + let handle = document.handles().unwrap_or("unknown"); 297 + let pds_url = document 298 + .pds_endpoints() 299 + .first() 300 + .cloned() 301 + .map(|value| value) 302 + .ok_or_else(|| AppError::OAuth("Identity does not have a PDS".to_string()))?; 303 + 304 + let session = Session { 305 + did, 306 + handle: handle.to_string(), 307 + pds_url: pds_url.to_string(), 308 + }; 309 + 310 + Ok::<_, AppError>((jar, session)) 311 + } 312 + .await; 313 + 314 + match result { 315 + Ok((jar, session)) => { 316 + let jar = jar.add_session(&session); 317 + (jar.into_jar(), Redirect::to("/")).into_response() 318 + } 319 + Err(e) => { 320 + // Log the actual error for debugging 321 + tracing::warn!("Login callback failed: {}", e); 322 + 323 + // Resolve creator's handle for template 324 + let creator_did = &state.config.creator_identity; 325 + let creator_handle = state 326 + .identity_resolver 327 + .resolve(creator_did) 328 + .await 329 + .ok() 330 + .and_then(|doc| doc.also_known_as.first().cloned()) 331 + .and_then(|aka| aka.strip_prefix("at://").map(|s| s.to_string())) 332 + .unwrap_or_else(|| creator_did.clone()); 333 + 334 + // Provide generic error message to user 335 + let user_error = "Unable to complete login. Please try again."; 336 + 337 + let template = state.templates.get_template("login.html").unwrap(); 338 + let rendered = template 339 + .render(context! { 340 + logged_in => false, 341 + creator_did => creator_did, 342 + creator_handle => creator_handle, 343 + error => Some(user_error), 344 + }) 345 + .unwrap_or_else(|e| { 346 + tracing::error!("Template render error: {}", e); 347 + "An error occurred while loading the page.".to_string() 348 + }); 349 + Html(rendered).into_response() 350 + } 351 + } 352 + } 353 + 354 + pub async fn logout(jar: SessionJar) -> (PrivateCookieJar, Redirect) { 355 + (jar.remove_session().into_jar(), Redirect::to("/?loggedout")) 356 + }
+139
src/handlers/handler_download.rs
··· 1 + use std::sync::Arc; 2 + 3 + use atproto_oauth::jwt::verify; 4 + use axum::{ 5 + body::Body, 6 + extract::{Path, Query, State}, 7 + http::{StatusCode, header}, 8 + response::Response, 9 + }; 10 + use cid::Cid; 11 + use serde::Deserialize; 12 + use tokio::fs::File; 13 + use tokio_util::io::ReaderStream; 14 + 15 + use crate::handlers::util_entitlements::check_entitlement; 16 + use crate::state::AppState; 17 + 18 + #[derive(Deserialize)] 19 + pub struct DownloadQuery { 20 + jwt: Option<String>, 21 + } 22 + 23 + /// Raw codec for CID (0x55) - same as used in catalog-manager 24 + const RAW_CODEC: u64 = 0x55; 25 + 26 + pub async fn download_file( 27 + State(state): State<Arc<AppState>>, 28 + Path(id): Path<String>, 29 + Query(query): Query<DownloadQuery>, 30 + ) -> Result<Response, StatusCode> { 31 + let parsed_cid = Cid::try_from(id.as_str()).map_err(|_| StatusCode::NOT_FOUND)?; 32 + if parsed_cid.codec() != RAW_CODEC { 33 + return Err(StatusCode::NOT_FOUND); 34 + } 35 + 36 + // Validate access: either via JWT or anonymous entitlement 37 + if let Some(jwt) = &query.jwt { 38 + // JWT-based access: validate the token 39 + let expected_htu = format!("https://{}/download/{}", state.config.http_external, id); 40 + 41 + let claims = verify(jwt, &state.config.download_key).map_err(|e| { 42 + tracing::warn!("JWT validation failed: {}", e); 43 + StatusCode::NOT_FOUND 44 + })?; 45 + 46 + let http_method = claims.jose.http_method.as_deref().unwrap_or(""); 47 + let http_uri = claims.jose.http_uri.as_deref().unwrap_or(""); 48 + 49 + if http_method != "GET" { 50 + tracing::warn!("Invalid htm claim: expected GET, got {}", http_method); 51 + return Err(StatusCode::NOT_FOUND); 52 + } 53 + 54 + if http_uri != expected_htu { 55 + tracing::warn!( 56 + "Invalid htu claim: expected {}, got {}", 57 + expected_htu, 58 + http_uri 59 + ); 60 + return Err(StatusCode::NOT_FOUND); 61 + } 62 + } else { 63 + // No JWT: check if anonymous users are entitled to this item 64 + let catalog_entry = state 65 + .config 66 + .catalog 67 + .iter() 68 + .find(|e| e.id == id) 69 + .ok_or_else(|| { 70 + tracing::warn!("Catalog entry not found: {}", id); 71 + StatusCode::NOT_FOUND 72 + })?; 73 + 74 + // Check entitlement with empty contexts (anonymous user) 75 + let entitled = check_entitlement(&state, &[], catalog_entry).await; 76 + 77 + if !entitled { 78 + tracing::warn!("Anonymous access denied for catalog item: {}", id); 79 + return Err(StatusCode::NOT_FOUND); 80 + } 81 + } 82 + 83 + let file_path = state.config.files_path.join(&id); 84 + 85 + if !file_path.starts_with(&state.config.files_path) { 86 + return Err(StatusCode::NOT_FOUND); 87 + } 88 + 89 + let file = File::open(&file_path).await.map_err(|e| { 90 + tracing::warn!("File not found: {} - {}", file_path.display(), e); 91 + StatusCode::NOT_FOUND 92 + })?; 93 + 94 + let metadata = file.metadata().await.map_err(|_| StatusCode::NOT_FOUND)?; 95 + let file_size = metadata.len(); 96 + 97 + let stream = ReaderStream::new(file); 98 + let body = Body::from_stream(stream); 99 + 100 + let content_type = state 101 + .config 102 + .catalog 103 + .iter() 104 + .find(|e| e.id == id) 105 + .and_then(|e| e.content_type.clone()) 106 + .unwrap_or_else(|| { 107 + mime_guess::from_path(&file_path) 108 + .first_or_octet_stream() 109 + .to_string() 110 + }); 111 + 112 + let filename = file_path 113 + .file_name() 114 + .and_then(|n| n.to_str()) 115 + .unwrap_or(&id); 116 + 117 + // Sanitize filename for Content-Disposition header to prevent header injection 118 + let sanitized_filename: String = filename 119 + .chars() 120 + .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_' || *c == '.') 121 + .collect(); 122 + let sanitized_filename = if sanitized_filename.is_empty() { 123 + "download".to_string() 124 + } else { 125 + sanitized_filename 126 + }; 127 + 128 + let response = Response::builder() 129 + .header(header::CONTENT_TYPE, content_type) 130 + .header(header::CONTENT_LENGTH, file_size) 131 + .header( 132 + header::CONTENT_DISPOSITION, 133 + format!("attachment; filename=\"{}\"", sanitized_filename), 134 + ) 135 + .body(body) 136 + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; 137 + 138 + Ok(response) 139 + }
+173
src/handlers/handler_getblob.rs
··· 1 + use std::sync::Arc; 2 + 3 + use atproto_identity::key::identify_key; 4 + use atproto_oauth::jwt::{Claims, verify}; 5 + use axum::{ 6 + body::Body, 7 + extract::{Query, State}, 8 + http::{HeaderMap, header}, 9 + response::Response, 10 + }; 11 + use base64::Engine as _; 12 + use base64::engine::general_purpose; 13 + use cid::Cid; 14 + use serde::Deserialize; 15 + use tokio::fs::File; 16 + use tokio_util::io::ReaderStream; 17 + 18 + use crate::handlers::util_entitlements::{check_entitlement, get_entitlement_contexts}; 19 + use crate::handlers::util_errors::GetBlobError; 20 + use crate::state::AppState; 21 + 22 + /// Raw codec for CID (0x55) - same as used in catalog-manager 23 + const RAW_CODEC: u64 = 0x55; 24 + 25 + #[derive(Deserialize)] 26 + pub struct GetBlobParams { 27 + /// The DID of the account (must match creator_identity) 28 + did: String, 29 + /// The CID of the blob to fetch (must exist in catalog) 30 + cid: String, 31 + } 32 + 33 + pub async fn get_blob( 34 + State(state): State<Arc<AppState>>, 35 + headers: HeaderMap, 36 + Query(params): Query<GetBlobParams>, 37 + ) -> Result<Response, GetBlobError> { 38 + // 1. Validate did matches creator_identity 39 + if params.did != state.config.creator_identity { 40 + return Err(GetBlobError::RepoNotFound); 41 + } 42 + 43 + // 2. Validate CID is a valid raw CID 44 + let parsed_cid = Cid::try_from(params.cid.as_str()).map_err(|_| GetBlobError::BlobNotFound)?; 45 + if parsed_cid.codec() != RAW_CODEC { 46 + return Err(GetBlobError::BlobNotFound); 47 + } 48 + 49 + // 3. Validate cid exists in catalog 50 + let catalog_entry = state 51 + .config 52 + .catalog 53 + .iter() 54 + .find(|e| e.id == params.cid) 55 + .ok_or(GetBlobError::BlobNotFound)?; 56 + 57 + // 4. Extract and validate Authorization header 58 + let caller_did = validate_authorization(&state, &headers).await?; 59 + 60 + // 5. Resolve caller's PDS from their DID document 61 + let doc = state 62 + .identity_resolver 63 + .resolve(&caller_did) 64 + .await 65 + .map_err(|_| GetBlobError::Unauthorized)?; 66 + 67 + let pds_endpoints = doc.pds_endpoints(); 68 + let pds_url = pds_endpoints.first().ok_or(GetBlobError::Unauthorized)?; 69 + 70 + // 6. Build entitlement contexts and check entitlement for the specific catalog item 71 + let contexts = get_entitlement_contexts(&state, &caller_did, None, pds_url).await; 72 + let is_entitled = check_entitlement(&state, &contexts, catalog_entry).await; 73 + 74 + if !is_entitled { 75 + return Err(GetBlobError::Unauthorized); 76 + } 77 + 78 + // 7. Serve the blob 79 + let file_path = state.config.files_path.join(&params.cid); 80 + 81 + // Security: ensure path doesn't escape files directory 82 + if !file_path.starts_with(&state.config.files_path) { 83 + return Err(GetBlobError::BlobNotFound); 84 + } 85 + 86 + let file = File::open(&file_path) 87 + .await 88 + .map_err(|_| GetBlobError::BlobNotFound)?; 89 + 90 + let metadata = file.metadata().await.map_err(|e| { 91 + tracing::error!(error = %e, "Failed to read blob metadata"); 92 + GetBlobError::Internal(e.to_string()) 93 + })?; 94 + 95 + let stream = ReaderStream::new(file); 96 + let body = Body::from_stream(stream); 97 + 98 + let content_type = catalog_entry.content_type.clone().unwrap_or_else(|| { 99 + mime_guess::from_path(&file_path) 100 + .first_or_octet_stream() 101 + .to_string() 102 + }); 103 + 104 + Response::builder() 105 + .header(header::CONTENT_TYPE, content_type) 106 + .header(header::CONTENT_LENGTH, metadata.len()) 107 + .body(body) 108 + .map_err(|e| GetBlobError::Internal(e.to_string())) 109 + } 110 + 111 + /// Validate the Authorization header and return the caller's DID. 112 + /// 113 + /// This implements the same logic as atproto-xrpcs Authorization extractor: 114 + /// 1. Extract Bearer token from Authorization header 115 + /// 2. Decode JWT claims to get issuer (caller's DID) 116 + /// 3. Resolve issuer's DID document 117 + /// 4. Verify JWT signature against DID document keys 118 + async fn validate_authorization( 119 + state: &AppState, 120 + headers: &HeaderMap, 121 + ) -> Result<String, GetBlobError> { 122 + // Extract Bearer token 123 + let auth_header = headers 124 + .get(header::AUTHORIZATION) 125 + .and_then(|value| value.to_str().ok()) 126 + .and_then(|s| s.strip_prefix("Bearer ")) 127 + .ok_or(GetBlobError::Unauthorized)?; 128 + 129 + let token = auth_header.trim(); 130 + 131 + // Decode claims to get issuer 132 + let parts: Vec<&str> = token.split('.').collect(); 133 + if parts.len() != 3 { 134 + return Err(GetBlobError::Unauthorized); 135 + } 136 + 137 + let claims_bytes = general_purpose::URL_SAFE_NO_PAD 138 + .decode(parts[1]) 139 + .map_err(|_| GetBlobError::Unauthorized)?; 140 + 141 + let claims: Claims = 142 + serde_json::from_slice(&claims_bytes).map_err(|_| GetBlobError::Unauthorized)?; 143 + 144 + let issuer = claims 145 + .jose 146 + .issuer 147 + .as_ref() 148 + .ok_or(GetBlobError::Unauthorized)?; 149 + 150 + // Resolve the issuer's DID document 151 + let did_document = state 152 + .identity_resolver 153 + .resolve(issuer) 154 + .await 155 + .map_err(|_| GetBlobError::Unauthorized)?; 156 + 157 + // Get verification keys from DID document 158 + let did_keys = did_document.did_keys(); 159 + if did_keys.is_empty() { 160 + return Err(GetBlobError::Unauthorized); 161 + } 162 + 163 + // Try to validate with each key 164 + for key_multibase in did_keys { 165 + if let Ok(key_data) = identify_key(key_multibase) { 166 + if verify(token, &key_data).is_ok() { 167 + return Ok(issuer.clone()); 168 + } 169 + } 170 + } 171 + 172 + Err(GetBlobError::Unauthorized) 173 + }
+141
src/handlers/handler_home.rs
··· 1 + use std::sync::Arc; 2 + use std::time::{SystemTime, UNIX_EPOCH}; 3 + 4 + use atproto_oauth::jwt::{Claims, Header, JoseClaims, mint}; 5 + use axum::{ 6 + extract::{Query, State}, 7 + response::Html, 8 + }; 9 + use minijinja::context; 10 + use serde::{Deserialize, Serialize}; 11 + 12 + use crate::handlers::util_entitlements::{check_entitlement, get_entitlement_contexts}; 13 + use crate::session::SessionJar; 14 + use crate::state::AppState; 15 + 16 + #[derive(Deserialize)] 17 + pub struct HomeQuery { 18 + #[serde(default)] 19 + loginrequired: Option<String>, 20 + #[serde(default)] 21 + loggedout: Option<String>, 22 + } 23 + 24 + /// Catalog item for template rendering with optional download URL. 25 + #[derive(Serialize)] 26 + struct CatalogItemView { 27 + id: String, 28 + name: String, 29 + description: String, 30 + download_url: Option<String>, 31 + icon: String, 32 + entitled: bool, 33 + } 34 + 35 + pub async fn home( 36 + State(state): State<Arc<AppState>>, 37 + jar: SessionJar, 38 + Query(query): Query<HomeQuery>, 39 + ) -> Html<String> { 40 + let session = jar.get_session(); 41 + let logged_in = session.is_some(); 42 + let handle = session 43 + .as_ref() 44 + .map(|s| s.handle.clone()) 45 + .unwrap_or_default(); 46 + 47 + // Resolve creator's handle from their DID 48 + let creator_did = &state.config.creator_identity; 49 + let creator_handle = state 50 + .identity_resolver 51 + .resolve(creator_did) 52 + .await 53 + .ok() 54 + .and_then(|doc| doc.also_known_as.first().cloned()) 55 + .and_then(|aka| aka.strip_prefix("at://").map(|s| s.to_string())) 56 + .unwrap_or_else(|| creator_did.clone()); 57 + 58 + // Build entitlement contexts if logged in 59 + let contexts = if let Some(ref session) = session { 60 + get_entitlement_contexts( 61 + &state, 62 + &session.did, 63 + Some(&session.handle), 64 + &session.pds_url, 65 + ) 66 + .await 67 + } else { 68 + vec![] 69 + }; 70 + 71 + // Build catalog items with per-item entitlement checks 72 + let mut catalog_items = Vec::new(); 73 + for entry in &state.config.catalog { 74 + let entitled = check_entitlement(&state, &contexts, entry).await; 75 + 76 + let download_url = if entitled { 77 + if let Some(ref s) = session { 78 + // Logged-in user: generate JWT-based download URL 79 + create_download_url(&state, &s.did, &entry.id).ok() 80 + } else { 81 + // Anonymous user entitled to this item: generate simple download URL 82 + Some(format!("/download/{}", entry.id)) 83 + } 84 + } else { 85 + None 86 + }; 87 + 88 + catalog_items.push(CatalogItemView { 89 + id: entry.id.clone(), 90 + name: entry.name.clone(), 91 + description: entry.description.clone(), 92 + download_url, 93 + icon: entry 94 + .icon 95 + .clone() 96 + .unwrap_or_else(|| "fa-regular fa-file".to_string()), 97 + entitled, 98 + }); 99 + } 100 + 101 + let template = state.templates.get_template("home.html").unwrap(); 102 + let rendered = template 103 + .render(context! { 104 + logged_in => logged_in, 105 + handle => handle, 106 + creator_did => creator_did, 107 + creator_handle => creator_handle, 108 + login_required => query.loginrequired.is_some(), 109 + logged_out => query.loggedout.is_some(), 110 + catalog => catalog_items, 111 + }) 112 + .unwrap_or_else(|e| { 113 + tracing::error!("Template render error: {}", e); 114 + "An error occurred while loading the page.".to_string() 115 + }); 116 + 117 + Html(rendered) 118 + } 119 + 120 + fn create_download_url(state: &AppState, did: &str, id: &str) -> anyhow::Result<String> { 121 + let htu = format!("https://{}/download/{}", state.config.http_external, id); 122 + let now = SystemTime::now() 123 + .duration_since(UNIX_EPOCH) 124 + .map_err(|e| anyhow::anyhow!("System time error: {}", e))? 125 + .as_secs(); 126 + let exp = now + 600; 127 + 128 + let header: Header = state.config.download_key.clone().try_into()?; 129 + let claims = Claims::new(JoseClaims { 130 + subject: Some(did.to_string()), 131 + expiration: Some(exp), 132 + issued_at: Some(now), 133 + http_method: Some("GET".to_string()), 134 + http_uri: Some(htu.clone()), 135 + ..Default::default() 136 + }); 137 + 138 + let token = mint(&state.config.download_key, &header, &claims)?; 139 + 140 + Ok(format!("{}?jwt={}", htu, token)) 141 + }
+109
src/handlers/handler_oauth.rs
··· 1 + use std::sync::Arc; 2 + 3 + use atproto_identity::key::to_public; 4 + use axum::{ 5 + extract::State, 6 + http::StatusCode, 7 + response::{IntoResponse, Json, Response}, 8 + }; 9 + use p256::elliptic_curve::sec1::ToEncodedPoint as _; 10 + use serde_json::{Value, json}; 11 + 12 + use crate::state::AppState; 13 + 14 + pub async fn client_metadata(State(state): State<Arc<AppState>>) -> Response { 15 + let public_key = match to_public(&state.config.oauth_client_credentials) { 16 + Ok(key) => key, 17 + Err(e) => { 18 + tracing::error!("Failed to derive public key: {}", e); 19 + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); 20 + } 21 + }; 22 + let public_key_str = public_key.to_string(); 23 + 24 + let jwk = match key_data_to_jwk(&state.config.oauth_client_credentials, &public_key_str) { 25 + Ok(jwk) => jwk, 26 + Err(e) => { 27 + tracing::error!("Failed to convert key to JWK: {}", e); 28 + return StatusCode::INTERNAL_SERVER_ERROR.into_response(); 29 + } 30 + }; 31 + 32 + Json(json!({ 33 + "application_type": "web", 34 + "client_id": state.config.client_id(), 35 + "client_name": "Magazi", 36 + "client_uri": state.config.external_url(), 37 + "dpop_bound_access_tokens": true, 38 + "grant_types": [ 39 + "authorization_code", 40 + "refresh_token" 41 + ], 42 + "jwks": { 43 + "keys": [jwk] 44 + }, 45 + "redirect_uris": [ 46 + state.config.redirect_uri() 47 + ], 48 + "response_types": [ 49 + "code" 50 + ], 51 + "scope": "atproto", 52 + "token_endpoint_auth_method": "private_key_jwt", 53 + "token_endpoint_auth_signing_alg": "ES256" 54 + })) 55 + .into_response() 56 + } 57 + 58 + fn key_data_to_jwk( 59 + key_data: &atproto_identity::key::KeyData, 60 + kid: &str, 61 + ) -> Result<Value, &'static str> { 62 + use atproto_identity::key::to_public; 63 + use base64::Engine; 64 + use base64::engine::general_purpose::URL_SAFE_NO_PAD; 65 + 66 + let public_key = to_public(key_data).map_err(|_| "Failed to derive public key")?; 67 + let bytes = public_key.bytes(); 68 + 69 + if bytes.len() == 65 && bytes[0] == 0x04 { 70 + let x = &bytes[1..33]; 71 + let y = &bytes[33..65]; 72 + 73 + Ok(json!({ 74 + "kty": "EC", 75 + "crv": "P-256", 76 + "alg": "ES256", 77 + "use": "sig", 78 + "kid": kid, 79 + "x": URL_SAFE_NO_PAD.encode(x), 80 + "y": URL_SAFE_NO_PAD.encode(y) 81 + })) 82 + } else if bytes.len() == 33 { 83 + use p256::elliptic_curve::sec1::FromEncodedPoint; 84 + use p256::{EncodedPoint, PublicKey}; 85 + 86 + let encoded_point = 87 + EncodedPoint::from_bytes(bytes).map_err(|_| "Invalid compressed point")?; 88 + let public_key = PublicKey::from_encoded_point(&encoded_point) 89 + .into_option() 90 + .ok_or("Failed to decompress point")?; 91 + let uncompressed = public_key.to_encoded_point(false); 92 + let uncompressed_bytes = uncompressed.as_bytes(); 93 + 94 + let x = &uncompressed_bytes[1..33]; 95 + let y = &uncompressed_bytes[33..65]; 96 + 97 + Ok(json!({ 98 + "kty": "EC", 99 + "crv": "P-256", 100 + "alg": "ES256", 101 + "use": "sig", 102 + "kid": kid, 103 + "x": URL_SAFE_NO_PAD.encode(x), 104 + "y": URL_SAFE_NO_PAD.encode(y) 105 + })) 106 + } else { 107 + Err("Unexpected key format") 108 + } 109 + }
+8
src/handlers/mod.rs
··· 1 + pub mod handler_auth; 2 + pub mod handler_download; 3 + pub mod handler_getblob; 4 + pub mod handler_home; 5 + pub mod handler_oauth; 6 + 7 + pub mod util_entitlements; 8 + pub mod util_errors;
+326
src/handlers/util_entitlements.rs
··· 1 + use std::collections::HashMap; 2 + 3 + use atproto_attestation::{AnyInput, verify_record}; 4 + use atproto_client::RecordResolver; 5 + use atproto_client::client::Auth; 6 + use atproto_client::com::atproto::repo::{ListRecordsParams, list_records}; 7 + use atproto_record::lexicon::com::atproto::repo::TypedStrongRef; 8 + use atproto_record::typed::{LexiconType, TypedLexicon}; 9 + use datalogic_rs::DataLogic; 10 + use serde::{Deserialize, Serialize}; 11 + 12 + use crate::config::CatalogEntry; 13 + use crate::entitlements::EntitlementContext; 14 + use crate::resolvers::DidKeyResolver; 15 + use crate::state::AppState; 16 + 17 + /// Cache key used for anonymous (unauthenticated) visitors 18 + const ANONYMOUS_CACHE_KEY: &str = ""; 19 + 20 + #[derive(Serialize, Deserialize, Clone, PartialEq)] 21 + struct SupporterProof { 22 + cid: String, 23 + signature: String, 24 + #[serde(flatten)] 25 + extra: HashMap<String, serde_json::Value>, 26 + } 27 + 28 + impl LexiconType for SupporterProof { 29 + fn lexicon_type() -> &'static str { 30 + "com.atprotofans.supporterProof" 31 + } 32 + } 33 + 34 + #[derive(Serialize, Deserialize, Clone, PartialEq)] 35 + struct BrokerProof { 36 + cid: String, 37 + signature: String, 38 + #[serde(flatten)] 39 + extra: HashMap<String, serde_json::Value>, 40 + } 41 + 42 + impl LexiconType for BrokerProof { 43 + fn lexicon_type() -> &'static str { 44 + "com.atprotofans.brokerProof" 45 + } 46 + } 47 + 48 + type TypedSupporterProof = TypedLexicon<SupporterProof>; 49 + type TypedBrokerProof = TypedLexicon<BrokerProof>; 50 + 51 + #[derive(Serialize, Deserialize, Clone, PartialEq)] 52 + #[serde(untagged)] 53 + enum SignatureOrRef { 54 + SupporterProof(TypedSupporterProof), 55 + BrokerProof(TypedBrokerProof), 56 + StrongRef(TypedStrongRef), 57 + } 58 + 59 + #[derive(Serialize, Deserialize, PartialEq)] 60 + struct SupporterRecord { 61 + subject: String, 62 + #[serde(flatten)] 63 + extra: HashMap<String, serde_json::Value>, 64 + #[serde(default)] 65 + signatures: Vec<SignatureOrRef>, 66 + } 67 + 68 + impl LexiconType for SupporterRecord { 69 + fn lexicon_type() -> &'static str { 70 + "com.atprotofans.supporter" 71 + } 72 + } 73 + 74 + type TypedSupporterRecord = TypedLexicon<SupporterRecord>; 75 + 76 + /// Build entitlement contexts for a user by fetching their supporter records 77 + /// and resolving all StrongRef proofs. 78 + /// 79 + /// Returns a list of contexts, one for each valid supporter record that has 80 + /// both a supporterProof and brokerProof StrongRef that can be resolved. 81 + pub async fn get_entitlement_contexts( 82 + state: &AppState, 83 + did: &str, 84 + handle: Option<&str>, 85 + pds_url: &str, 86 + ) -> Vec<EntitlementContext> { 87 + // Check the context cache first 88 + if let Some(cached) = state.context_cache.get(did).await { 89 + return cached; 90 + } 91 + 92 + let contexts = build_entitlement_contexts(state, did, handle, pds_url).await; 93 + 94 + // Cache the result 95 + state 96 + .context_cache 97 + .insert(did.to_string(), contexts.clone()) 98 + .await; 99 + 100 + contexts 101 + } 102 + 103 + async fn build_entitlement_contexts( 104 + state: &AppState, 105 + did: &str, 106 + handle: Option<&str>, 107 + pds_url: &str, 108 + ) -> Vec<EntitlementContext> { 109 + // Always include a base authenticated context for logged-in users. 110 + // This ensures that rules checking only `authenticated: true` will match 111 + // even if the user has no supporter records. 112 + let mut contexts = vec![EntitlementContext::authenticated( 113 + did.to_string(), 114 + handle.map(|s| s.to_string()), 115 + )]; 116 + 117 + let params = ListRecordsParams::new().limit(100); 118 + 119 + let records = match list_records::<TypedSupporterRecord>( 120 + &state.http_client, 121 + &Auth::None, 122 + pds_url, 123 + did.to_string(), 124 + "com.atprotofans.supporter".to_string(), 125 + params, 126 + ) 127 + .await 128 + { 129 + Ok(r) => r, 130 + Err(e) => { 131 + tracing::error!("Failed to list supporter records: {}", e); 132 + return contexts; 133 + } 134 + }; 135 + 136 + for record in records.records { 137 + // Find StrongRef for supporterProof from creator 138 + let supporter_proof_ref = find_strong_ref( 139 + &record.value, 140 + &state.config.creator_identity, 141 + "com.atprotofans.supporterProof", 142 + ); 143 + 144 + // Find StrongRef for brokerProof from broker 145 + let broker_proof_ref = find_strong_ref( 146 + &record.value, 147 + &state.config.broker_identity, 148 + "com.atprotofans.brokerProof", 149 + ); 150 + 151 + // Skip records that don't have both StrongRefs 152 + let (supporter_ref, broker_ref) = match (supporter_proof_ref, broker_proof_ref) { 153 + (Some(sp), Some(bp)) => (sp, bp), 154 + _ => continue, 155 + }; 156 + 157 + // Verify the attestation on the supporter record 158 + let verify_result = verify_record( 159 + AnyInput::Serialize(&record.value), 160 + did, 161 + DidKeyResolver::new(state.identity_resolver.clone()), 162 + (*state.record_resolver).clone(), 163 + ) 164 + .await; 165 + 166 + if verify_result.is_err() { 167 + continue; 168 + } 169 + 170 + // Fetch the actual proof records via the StrongRefs 171 + let supporter_proof: Option<serde_json::Value> = 172 + match state.record_resolver.resolve(&supporter_ref).await { 173 + Ok(proof) => Some(proof), 174 + Err(_) => None, 175 + }; 176 + 177 + let broker_proof: Option<serde_json::Value> = 178 + match state.record_resolver.resolve(&broker_ref).await { 179 + Ok(proof) => Some(proof), 180 + Err(_) => None, 181 + }; 182 + 183 + // Only create context if both proofs were fetched successfully 184 + if let (Some(sp), Some(bp)) = (supporter_proof, broker_proof) { 185 + // Convert the supporter record to a JSON value 186 + let supporter_json = 187 + serde_json::to_value(&record.value).unwrap_or(serde_json::Value::Null); 188 + 189 + contexts.push(EntitlementContext::with_supporter( 190 + did.to_string(), 191 + handle.map(|s| s.to_string()), 192 + supporter_json, 193 + sp, 194 + bp, 195 + )); 196 + } 197 + } 198 + 199 + contexts 200 + } 201 + 202 + /// Find a StrongRef in the signatures array that points to the expected authority and collection. 203 + fn find_strong_ref( 204 + record: &TypedSupporterRecord, 205 + expected_authority: &str, 206 + collection: &str, 207 + ) -> Option<String> { 208 + for sig in &record.inner.signatures { 209 + if let SignatureOrRef::StrongRef(strong_ref) = sig { 210 + let expected_prefix = format!("at://{}/{}/", expected_authority, collection); 211 + if strong_ref.inner.uri.starts_with(&expected_prefix) { 212 + return Some(strong_ref.inner.uri.clone()); 213 + } 214 + } 215 + } 216 + None 217 + } 218 + 219 + /// Check if a user is entitled to a specific catalog entry by evaluating its 220 + /// JSONLogic requirements against each of their entitlement contexts. 221 + /// 222 + /// Returns true if ANY context satisfies the requirements. 223 + pub async fn check_entitlement( 224 + state: &AppState, 225 + contexts: &[EntitlementContext], 226 + catalog_entry: &CatalogEntry, 227 + ) -> bool { 228 + // Determine cache key - use empty string for anonymous users 229 + let cache_did = contexts 230 + .first() 231 + .and_then(|ctx| ctx.did.clone()) 232 + .unwrap_or_else(|| ANONYMOUS_CACHE_KEY.to_string()); 233 + 234 + let cache_key = (cache_did.clone(), catalog_entry.id.clone()); 235 + 236 + // Check the entitlement cache first 237 + if let Some(cached) = state.entitlement_cache.get(&cache_key).await { 238 + return cached; 239 + } 240 + 241 + let result = evaluate_entitlement(contexts, catalog_entry); 242 + 243 + // Cache the result 244 + state.entitlement_cache.insert(cache_key, result).await; 245 + 246 + result 247 + } 248 + 249 + fn evaluate_entitlement(contexts: &[EntitlementContext], catalog_entry: &CatalogEntry) -> bool { 250 + let engine = DataLogic::new(); 251 + 252 + // Compile the rule once 253 + let compiled = match engine.compile(&catalog_entry.requirements) { 254 + Ok(c) => c, 255 + Err(e) => { 256 + tracing::error!( 257 + catalog_id = %catalog_entry.id, 258 + error = %e, 259 + "Failed to compile JSONLogic rule" 260 + ); 261 + return false; 262 + } 263 + }; 264 + 265 + // If no contexts provided (unauthenticated), create an unauthenticated context 266 + let contexts_to_check: Vec<EntitlementContext> = if contexts.is_empty() { 267 + vec![EntitlementContext::unauthenticated()] 268 + } else { 269 + contexts.to_vec() 270 + }; 271 + 272 + // Check each context against the requirements 273 + for context in contexts_to_check.iter() { 274 + let context_json = match serde_json::to_value(context) { 275 + Ok(v) => v, 276 + Err(e) => { 277 + tracing::error!("Failed to serialize context: {}", e); 278 + continue; 279 + } 280 + }; 281 + 282 + match engine.evaluate_owned(&compiled, context_json) { 283 + Ok(result) => { 284 + // JSONLogic returns a JSON value, check if it's truthy 285 + if is_truthy(&result) { 286 + return true; 287 + } 288 + } 289 + Err(e) => { 290 + tracing::error!( 291 + catalog_id = %catalog_entry.id, 292 + error = %e, 293 + "Failed to evaluate JSONLogic rule" 294 + ); 295 + } 296 + } 297 + } 298 + 299 + false 300 + } 301 + 302 + /// Check if a JSON value is truthy according to JSONLogic semantics. 303 + fn is_truthy(value: &serde_json::Value) -> bool { 304 + match value { 305 + serde_json::Value::Null => false, 306 + serde_json::Value::Bool(b) => *b, 307 + serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false), 308 + serde_json::Value::String(s) => !s.is_empty(), 309 + serde_json::Value::Array(a) => !a.is_empty(), 310 + serde_json::Value::Object(_) => true, 311 + } 312 + } 313 + 314 + /// Pre-populate the entitlement cache for anonymous visitors. 315 + /// 316 + /// This should be called on application startup to ensure the first 317 + /// anonymous visitor gets a fast response. 318 + pub async fn warmup_anonymous_cache(state: &AppState) { 319 + let empty_contexts: Vec<EntitlementContext> = vec![]; 320 + 321 + for entry in &state.config.catalog { 322 + let result = evaluate_entitlement(&empty_contexts, entry); 323 + let cache_key = (ANONYMOUS_CACHE_KEY.to_string(), entry.id.clone()); 324 + state.entitlement_cache.insert(cache_key, result).await; 325 + } 326 + }
+44
src/handlers/util_errors.rs
··· 1 + use axum::{ 2 + Json, 3 + http::StatusCode, 4 + response::{IntoResponse, Response}, 5 + }; 6 + use serde::Serialize; 7 + 8 + #[derive(Serialize)] 9 + pub struct XrpcError { 10 + pub error: String, 11 + #[serde(skip_serializing_if = "Option::is_none")] 12 + pub message: Option<String>, 13 + } 14 + 15 + pub enum GetBlobError { 16 + BlobNotFound, 17 + RepoNotFound, 18 + Unauthorized, 19 + Internal(String), 20 + } 21 + 22 + impl IntoResponse for GetBlobError { 23 + fn into_response(self) -> Response { 24 + let (status, error, message) = match self { 25 + Self::BlobNotFound => (StatusCode::NOT_FOUND, "BlobNotFound", None), 26 + Self::RepoNotFound => (StatusCode::NOT_FOUND, "RepoNotFound", None), 27 + Self::Unauthorized => (StatusCode::UNAUTHORIZED, "AuthRequired", None), 28 + Self::Internal(msg) => ( 29 + StatusCode::INTERNAL_SERVER_ERROR, 30 + "InternalError", 31 + Some(msg), 32 + ), 33 + }; 34 + 35 + ( 36 + status, 37 + Json(XrpcError { 38 + error: error.to_string(), 39 + message, 40 + }), 41 + ) 42 + .into_response() 43 + } 44 + }
+104
src/main.rs
··· 1 + mod config; 2 + mod entitlements; 3 + mod error; 4 + mod handlers; 5 + mod oauth_storage; 6 + mod resolvers; 7 + mod session; 8 + mod state; 9 + mod templates; 10 + 11 + use std::net::SocketAddr; 12 + use std::sync::Arc; 13 + 14 + use axum::{ 15 + Router, 16 + http::{HeaderValue, header}, 17 + routing::{get, post}, 18 + }; 19 + use tower_http::{ 20 + limit::RequestBodyLimitLayer, set_header::SetResponseHeaderLayer, trace::TraceLayer, 21 + }; 22 + use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 23 + 24 + use crate::config::Config; 25 + use crate::state::AppState; 26 + 27 + #[tokio::main] 28 + async fn main() -> anyhow::Result<()> { 29 + tracing_subscriber::registry() 30 + .with( 31 + tracing_subscriber::EnvFilter::try_from_default_env() 32 + .unwrap_or_else(|_| "magazi=warn".into()), 33 + ) 34 + .with(tracing_subscriber::fmt::layer()) 35 + .init(); 36 + 37 + let config = Config::from_env()?; 38 + 39 + // Verify all catalog files exist 40 + for entry in &config.catalog { 41 + let file_path = config.files_path.join(&entry.id); 42 + if !file_path.exists() { 43 + panic!( 44 + "Catalog entry '{}' references missing file: {}", 45 + entry.name, 46 + file_path.display() 47 + ); 48 + } 49 + } 50 + 51 + let http_port = config.http_port; 52 + let state = AppState::new(config).await?; 53 + let state = Arc::new(state); 54 + 55 + // Warm up the entitlement cache for anonymous visitors 56 + handlers::util_entitlements::warmup_anonymous_cache(&state).await; 57 + 58 + let app = Router::new() 59 + .route("/", get(handlers::handler_home::home)) 60 + .route( 61 + "/oauth-client-metadata.json", 62 + get(handlers::handler_oauth::client_metadata), 63 + ) 64 + .route("/login", get(handlers::handler_auth::login_form)) 65 + .route("/login", post(handlers::handler_auth::login_start)) 66 + .route("/login/callback", get(handlers::handler_auth::login_callback)) 67 + .route("/download/{id}", get(handlers::handler_download::download_file)) 68 + .route( 69 + "/xrpc/com.atproto.sync.getBlob", 70 + get(handlers::handler_getblob::get_blob), 71 + ) 72 + .route("/logout", get(handlers::handler_auth::logout)) 73 + // Security headers 74 + .layer(SetResponseHeaderLayer::overriding( 75 + header::X_CONTENT_TYPE_OPTIONS, 76 + HeaderValue::from_static("nosniff"), 77 + )) 78 + .layer(SetResponseHeaderLayer::overriding( 79 + header::X_FRAME_OPTIONS, 80 + HeaderValue::from_static("DENY"), 81 + )) 82 + .layer(SetResponseHeaderLayer::overriding( 83 + header::CONTENT_SECURITY_POLICY, 84 + HeaderValue::from_static( 85 + "default-src 'self'; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src https://cdnjs.cloudflare.com", 86 + ), 87 + )) 88 + .layer(SetResponseHeaderLayer::overriding( 89 + header::STRICT_TRANSPORT_SECURITY, 90 + HeaderValue::from_static("max-age=31536000; includeSubDomains"), 91 + )) 92 + // Request body size limit (1KB for form submissions) 93 + .layer(RequestBodyLimitLayer::new(1024)) 94 + .layer(TraceLayer::new_for_http()) 95 + .with_state(state); 96 + 97 + let addr = SocketAddr::from(([0, 0, 0, 0], http_port)); 98 + tracing::info!("listening on {}", addr); 99 + 100 + let listener = tokio::net::TcpListener::bind(addr).await?; 101 + axum::serve(listener, app).await?; 102 + 103 + Ok(()) 104 + }
+62
src/oauth_storage.rs
··· 1 + use std::collections::HashMap; 2 + use std::sync::Arc; 3 + 4 + use anyhow::Result; 5 + use async_trait::async_trait; 6 + use atproto_oauth::storage::OAuthRequestStorage; 7 + use atproto_oauth::workflow::OAuthRequest; 8 + use chrono::Utc; 9 + use tokio::sync::RwLock; 10 + 11 + pub struct InMemoryOAuthStorage { 12 + requests: RwLock<HashMap<String, OAuthRequest>>, 13 + } 14 + 15 + impl InMemoryOAuthStorage { 16 + pub fn new() -> Arc<Self> { 17 + Arc::new(Self { 18 + requests: RwLock::new(HashMap::new()), 19 + }) 20 + } 21 + } 22 + 23 + #[async_trait] 24 + impl OAuthRequestStorage for InMemoryOAuthStorage { 25 + async fn get_oauth_request_by_state(&self, state: &str) -> Result<Option<OAuthRequest>> { 26 + let requests = self.requests.read().await; 27 + Ok(requests.get(state).cloned()) 28 + } 29 + 30 + async fn insert_oauth_request(&self, request: OAuthRequest) -> Result<()> { 31 + let mut requests = self.requests.write().await; 32 + requests.insert(request.oauth_state.clone(), request); 33 + Ok(()) 34 + } 35 + 36 + async fn delete_oauth_request_by_state(&self, state: &str) -> Result<()> { 37 + let mut requests = self.requests.write().await; 38 + requests.remove(state); 39 + Ok(()) 40 + } 41 + 42 + async fn clear_expired_oauth_requests(&self) -> Result<u64> { 43 + let mut requests = self.requests.write().await; 44 + let now = Utc::now(); 45 + 46 + // Collect expired request states 47 + let expired_states: Vec<String> = requests 48 + .iter() 49 + .filter(|(_, req)| req.expires_at <= now) 50 + .map(|(state, _)| state.clone()) 51 + .collect(); 52 + 53 + let expired_count = expired_states.len(); 54 + 55 + // Remove expired requests and their associated contexts 56 + for state in expired_states { 57 + requests.remove(&state); 58 + } 59 + 60 + Ok(expired_count as u64) 61 + } 62 + }
+190
src/resolvers.rs
··· 1 + use std::sync::Arc; 2 + use std::time::Duration; 3 + 4 + use anyhow::Result; 5 + use async_trait::async_trait; 6 + use atproto_client::RecordResolver; 7 + use atproto_client::client::Auth; 8 + use atproto_client::com::atproto::repo::{GetRecordResponse, get_record}; 9 + use atproto_identity::key::{KeyData, identify_key}; 10 + use atproto_identity::model::{Document, VerificationMethod}; 11 + use atproto_identity::resolve::{HickoryDnsResolver, InnerIdentityResolver}; 12 + use atproto_identity::traits::{IdentityResolver, KeyResolver}; 13 + use moka::future::Cache; 14 + use reqwest::Client; 15 + 16 + pub struct CachingIdentityResolver { 17 + inner: InnerIdentityResolver, 18 + cache: Cache<String, Document>, 19 + } 20 + 21 + impl CachingIdentityResolver { 22 + pub fn new(http_client: Client, dns_resolver: Arc<HickoryDnsResolver>) -> Self { 23 + let inner = InnerIdentityResolver { 24 + http_client, 25 + dns_resolver, 26 + plc_hostname: "plc.directory".to_string(), 27 + }; 28 + 29 + let cache = Cache::builder() 30 + .max_capacity(1000) 31 + .time_to_live(Duration::from_hours(2)) 32 + .build(); 33 + 34 + Self { inner, cache } 35 + } 36 + } 37 + 38 + #[async_trait] 39 + impl IdentityResolver for CachingIdentityResolver { 40 + async fn resolve(&self, subject: &str) -> Result<Document> { 41 + if let Some(doc) = self.cache.get(subject).await { 42 + return Ok(doc); 43 + } 44 + 45 + let doc = self.inner.resolve(subject).await?; 46 + self.cache.insert(subject.to_string(), doc.clone()).await; 47 + Ok(doc) 48 + } 49 + } 50 + 51 + #[derive(Clone)] 52 + pub struct CachingRecordResolver { 53 + http_client: Client, 54 + identity_resolver: Arc<dyn IdentityResolver>, 55 + cache: Cache<String, serde_json::Value>, 56 + } 57 + 58 + impl CachingRecordResolver { 59 + pub fn new(http_client: Client, identity_resolver: Arc<dyn IdentityResolver>) -> Self { 60 + let cache = Cache::builder() 61 + .max_capacity(500) 62 + .time_to_live(Duration::from_mins(30)) 63 + .build(); 64 + 65 + Self { 66 + http_client, 67 + identity_resolver, 68 + cache, 69 + } 70 + } 71 + 72 + fn parse_aturi(aturi: &str) -> Option<(String, String, String)> { 73 + let stripped = aturi.strip_prefix("at://")?; 74 + let parts: Vec<&str> = stripped.splitn(3, '/').collect(); 75 + if parts.len() == 3 { 76 + Some(( 77 + parts[0].to_string(), 78 + parts[1].to_string(), 79 + parts[2].to_string(), 80 + )) 81 + } else { 82 + None 83 + } 84 + } 85 + } 86 + 87 + #[async_trait] 88 + impl RecordResolver for CachingRecordResolver { 89 + async fn resolve<T>(&self, aturi: &str) -> Result<T> 90 + where 91 + T: serde::de::DeserializeOwned + Send, 92 + { 93 + if let Some(cached) = self.cache.get(aturi).await { 94 + return serde_json::from_value(cached).map_err(|e| anyhow::anyhow!("{}", e)); 95 + } 96 + 97 + let (did, collection, rkey) = 98 + Self::parse_aturi(aturi).ok_or_else(|| anyhow::anyhow!("Invalid AT-URI: {}", aturi))?; 99 + 100 + let doc = self.identity_resolver.resolve(&did).await?; 101 + let pds_endpoints = doc.pds_endpoints(); 102 + let pds_url = pds_endpoints 103 + .first() 104 + .ok_or_else(|| anyhow::anyhow!("No PDS endpoint found for {}", did))?; 105 + 106 + let response = get_record( 107 + &self.http_client, 108 + &Auth::None, 109 + pds_url, 110 + &did, 111 + &collection, 112 + &rkey, 113 + None, 114 + ) 115 + .await?; 116 + 117 + match response { 118 + GetRecordResponse::Record { value, .. } => { 119 + self.cache.insert(aturi.to_string(), value.clone()).await; 120 + serde_json::from_value(value).map_err(|e| anyhow::anyhow!("{}", e)) 121 + } 122 + GetRecordResponse::Error(e) => { 123 + Err(anyhow::anyhow!("Record error: {:?}", e.error_message())) 124 + } 125 + } 126 + } 127 + } 128 + 129 + pub struct DidKeyResolver { 130 + identity_resolver: Arc<dyn IdentityResolver>, 131 + } 132 + 133 + impl DidKeyResolver { 134 + pub fn new(identity_resolver: Arc<dyn IdentityResolver>) -> Self { 135 + Self { identity_resolver } 136 + } 137 + } 138 + 139 + #[async_trait] 140 + impl KeyResolver for DidKeyResolver { 141 + async fn resolve(&self, key: &str) -> Result<KeyData> { 142 + if key.starts_with("did:key:") { 143 + identify_key(key).map_err(|e| anyhow::anyhow!("{}", e)) 144 + } else if key.starts_with("did:web:") || key.starts_with("did:plc:") { 145 + let (did, fragment) = key.split_once('#').ok_or_else(|| { 146 + anyhow::anyhow!("DID URL must contain a fragment (e.g., did:plc:xyz#atproto)") 147 + })?; 148 + 149 + if fragment.is_empty() { 150 + return Err(anyhow::anyhow!("DID URL fragment cannot be empty")); 151 + } 152 + 153 + let document = self.identity_resolver.resolve(did).await?; 154 + let fragment_with_hash = format!("#{}", fragment); 155 + 156 + let public_key_multibase = document 157 + .verification_method 158 + .iter() 159 + .find_map(|method| match method { 160 + VerificationMethod::Multikey { 161 + id, 162 + public_key_multibase, 163 + .. 164 + } if id == key || *id == fragment_with_hash => { 165 + Some(public_key_multibase.clone()) 166 + } 167 + _ => None, 168 + }) 169 + .ok_or_else(|| { 170 + anyhow::anyhow!( 171 + "Verification method '{}' not found in DID document '{}'", 172 + fragment, 173 + did 174 + ) 175 + })?; 176 + 177 + let full_key = if public_key_multibase.starts_with("did:key:") { 178 + public_key_multibase 179 + } else { 180 + format!("did:key:{}", public_key_multibase) 181 + }; 182 + 183 + identify_key(&full_key).map_err(|e| anyhow::anyhow!("{}", e)) 184 + } else { 185 + Err(anyhow::anyhow!( 186 + "Unsupported DID method. Supported: did:key:, did:web:, did:plc:" 187 + )) 188 + } 189 + } 190 + }
+89
src/session.rs
··· 1 + use std::sync::Arc; 2 + 3 + use axum::{extract::FromRequestParts, http::request::Parts}; 4 + use axum_extra::extract::cookie::{Cookie, Key, PrivateCookieJar}; 5 + use serde::{Deserialize, Serialize}; 6 + use time::Duration; 7 + 8 + use crate::state::AppState; 9 + 10 + pub const SESSION_COOKIE_NAME: &str = "magazi_session"; 11 + 12 + #[derive(Clone, Debug, Serialize, Deserialize)] 13 + pub struct Session { 14 + pub did: String, 15 + pub handle: String, 16 + pub pds_url: String, 17 + } 18 + 19 + impl Session { 20 + pub fn to_cookie(&self) -> String { 21 + serde_json::to_string(self).unwrap_or_default() 22 + } 23 + 24 + pub fn from_cookie(value: &str) -> Option<Self> { 25 + serde_json::from_str(value).ok() 26 + } 27 + } 28 + 29 + pub struct SessionJar { 30 + pub jar: PrivateCookieJar, 31 + } 32 + 33 + impl SessionJar { 34 + pub fn get_session(&self) -> Option<Session> { 35 + self.jar 36 + .get(SESSION_COOKIE_NAME) 37 + .and_then(|cookie| Session::from_cookie(cookie.value())) 38 + } 39 + 40 + pub fn add_session(self, session: &Session) -> Self { 41 + let cookie = create_session_cookie(session); 42 + Self { 43 + jar: self.jar.add(cookie), 44 + } 45 + } 46 + 47 + pub fn remove_session(self) -> Self { 48 + Self { 49 + jar: self.jar.remove(Cookie::from(SESSION_COOKIE_NAME)), 50 + } 51 + } 52 + 53 + pub fn into_jar(self) -> PrivateCookieJar { 54 + self.jar 55 + } 56 + } 57 + 58 + impl FromRequestParts<Arc<AppState>> for SessionJar { 59 + type Rejection = std::convert::Infallible; 60 + 61 + async fn from_request_parts( 62 + parts: &mut Parts, 63 + state: &Arc<AppState>, 64 + ) -> Result<Self, Self::Rejection> { 65 + let key = state.cookie_key.clone(); 66 + let jar = PrivateCookieJar::from_headers(&parts.headers, key); 67 + Ok(SessionJar { jar }) 68 + } 69 + } 70 + 71 + pub fn create_session_cookie(session: &Session) -> Cookie<'static> { 72 + Cookie::build((SESSION_COOKIE_NAME, session.to_cookie())) 73 + .path("/") 74 + .http_only(true) 75 + .secure(true) 76 + .same_site(axum_extra::extract::cookie::SameSite::Lax) 77 + .max_age(Duration::days(90)) 78 + .build() 79 + } 80 + 81 + pub fn cookie_key_from_bytes(bytes: &[u8]) -> Key { 82 + if bytes.len() < 64 { 83 + panic!( 84 + "HTTP_COOKIE_KEY must be at least 64 bytes, got {} bytes", 85 + bytes.len() 86 + ); 87 + } 88 + Key::from(bytes) 89 + }
+90
src/state.rs
··· 1 + use std::sync::Arc; 2 + use std::time::Duration; 3 + 4 + use anyhow::Result; 5 + use atproto_identity::resolve::HickoryDnsResolver; 6 + use atproto_identity::traits::IdentityResolver; 7 + use atproto_oauth::workflow::OAuthClient; 8 + use axum_extra::extract::cookie::Key; 9 + use minijinja::Environment; 10 + use moka::future::Cache; 11 + use reqwest::Client; 12 + 13 + use crate::config::Config; 14 + use crate::entitlements::EntitlementContext; 15 + use crate::oauth_storage::InMemoryOAuthStorage; 16 + use crate::resolvers::{CachingIdentityResolver, CachingRecordResolver}; 17 + use crate::session::cookie_key_from_bytes; 18 + use crate::templates::create_template_env; 19 + 20 + pub struct AppState { 21 + pub config: Config, 22 + pub http_client: Client, 23 + pub oauth_client: OAuthClient, 24 + pub oauth_storage: Arc<InMemoryOAuthStorage>, 25 + pub identity_resolver: Arc<dyn IdentityResolver>, 26 + pub record_resolver: Arc<CachingRecordResolver>, 27 + /// Cache of entitlement contexts by DID - 30 min TTL 28 + pub context_cache: Cache<String, Vec<EntitlementContext>>, 29 + /// Cache of entitlement results by (DID, catalog_id) - 5 min TTL 30 + pub entitlement_cache: Cache<(String, String), bool>, 31 + pub cookie_key: Key, 32 + pub templates: Environment<'static>, 33 + } 34 + 35 + impl AppState { 36 + pub async fn new(config: Config) -> Result<Self> { 37 + let http_client = Client::builder() 38 + .timeout(std::time::Duration::from_secs(30)) 39 + .build()?; 40 + 41 + let oauth_client = OAuthClient { 42 + redirect_uri: config.redirect_uri(), 43 + client_id: config.client_id(), 44 + private_signing_key_data: config.oauth_client_credentials.clone(), 45 + }; 46 + 47 + let oauth_storage = InMemoryOAuthStorage::new(); 48 + 49 + let dns_resolver = Arc::new(HickoryDnsResolver::create_resolver(&[])); 50 + 51 + let identity_resolver: Arc<dyn IdentityResolver> = Arc::new(CachingIdentityResolver::new( 52 + http_client.clone(), 53 + dns_resolver.clone(), 54 + )); 55 + 56 + let record_resolver = Arc::new(CachingRecordResolver::new( 57 + http_client.clone(), 58 + identity_resolver.clone(), 59 + )); 60 + 61 + // Cache entitlement contexts by DID - 30 min TTL 62 + let context_cache = Cache::builder() 63 + .max_capacity(10_000) 64 + .time_to_live(Duration::from_secs(1800)) // 30 minutes 65 + .build(); 66 + 67 + // Cache entitlement results by (DID, catalog_id) - 5 min TTL 68 + let entitlement_cache = Cache::builder() 69 + .max_capacity(50_000) 70 + .time_to_live(Duration::from_secs(300)) // 5 minutes 71 + .build(); 72 + 73 + let cookie_key = cookie_key_from_bytes(&config.cookie_key); 74 + 75 + let templates = create_template_env(); 76 + 77 + Ok(Self { 78 + config, 79 + http_client, 80 + oauth_client, 81 + oauth_storage, 82 + identity_resolver, 83 + record_resolver, 84 + context_cache, 85 + entitlement_cache, 86 + cookie_key, 87 + templates, 88 + }) 89 + } 90 + }
+22
src/templates.rs
··· 1 + use minijinja::Environment; 2 + use rust_embed::Embed; 3 + 4 + #[derive(Embed)] 5 + #[folder = "templates/"] 6 + #[prefix = ""] // optional: strips or adds path prefix 7 + struct Templates; 8 + 9 + pub fn create_template_env() -> Environment<'static> { 10 + let mut env = Environment::new(); 11 + 12 + for filename in Templates::iter() { 13 + if let Some(file) = Templates::get(&filename) { 14 + let content = 15 + std::str::from_utf8(file.data.as_ref()).expect("Template file is not valid UTF-8"); 16 + env.add_template_owned(filename.to_string(), content.to_owned()) 17 + .unwrap_or_else(|e| panic!("Failed to load template {}: {}", filename, e)); 18 + } 19 + } 20 + 21 + env 22 + }
+215
templates/base.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + 4 + <head> 5 + <meta charset="UTF-8"> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 + <title>{% block title %}Magazi{% endblock %}</title> 8 + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" 9 + integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" 10 + crossorigin="anonymous" referrerpolicy="no-referrer" /> 11 + <style> 12 + :root { 13 + --color-text: #333; 14 + --color-text-muted: #666; 15 + --color-primary: #0066cc; 16 + --color-primary-hover: #0052a3; 17 + --color-heading: #1a1a2e; 18 + --color-border: #ddd; 19 + --color-success: #28a745; 20 + --color-success-hover: #218838; 21 + --color-bg: #f5f5f5; 22 + --color-bg-card: #fafafa; 23 + --radius-sm: 4px; 24 + --radius-md: 8px; 25 + } 26 + 27 + * { 28 + box-sizing: border-box; 29 + margin: 0; 30 + padding: 0; 31 + } 32 + 33 + body { 34 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; 35 + line-height: 1.6; 36 + color: var(--color-text); 37 + max-width: 800px; 38 + margin: 0 auto; 39 + padding: 2rem; 40 + background: var(--color-bg); 41 + } 42 + 43 + header { 44 + padding-bottom: 1rem; 45 + } 46 + 47 + header h1 { 48 + font-size: 1.5rem; 49 + color: var(--color-heading); 50 + } 51 + 52 + nav { 53 + margin-top: 0.5rem; 54 + } 55 + 56 + nav a { 57 + color: var(--color-primary); 58 + text-decoration: none; 59 + margin-right: 1rem; 60 + } 61 + 62 + nav a:hover { 63 + text-decoration: underline; 64 + } 65 + 66 + main { 67 + background: white; 68 + padding: 2rem; 69 + border-radius: var(--radius-md); 70 + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 71 + } 72 + 73 + .alert { 74 + padding: 1rem; 75 + border-radius: var(--radius-sm); 76 + margin-bottom: 1rem; 77 + } 78 + 79 + .error { 80 + background: #fee; 81 + border: 1px solid #fcc; 82 + color: #c00; 83 + } 84 + 85 + .success { 86 + background: #efe; 87 + border: 1px solid #cfc; 88 + color: #060; 89 + } 90 + 91 + form { 92 + margin: 1rem 0; 93 + } 94 + 95 + label { 96 + display: block; 97 + margin-bottom: 0.5rem; 98 + font-weight: 500; 99 + } 100 + 101 + input[type="text"] { 102 + width: 100%; 103 + padding: 0.75rem; 104 + border: 1px solid var(--color-border); 105 + border-radius: var(--radius-sm); 106 + font-size: 1rem; 107 + margin-bottom: 1rem; 108 + } 109 + 110 + input[type="text"]:focus { 111 + outline: none; 112 + border-color: var(--color-primary); 113 + box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1); 114 + } 115 + 116 + button { 117 + background: var(--color-primary); 118 + color: white; 119 + border: none; 120 + padding: 0.75rem 1.5rem; 121 + border-radius: var(--radius-sm); 122 + font-size: 1rem; 123 + cursor: pointer; 124 + } 125 + 126 + button:hover { 127 + background: var(--color-primary-hover); 128 + } 129 + 130 + .catalog-item { 131 + border: 1px solid var(--color-border); 132 + border-radius: var(--radius-md); 133 + padding: 1.5rem; 134 + margin-bottom: 1rem; 135 + background: var(--color-bg-card); 136 + display: flex; 137 + gap: 1rem; 138 + } 139 + 140 + .catalog-item-icon { 141 + font-size: 2rem; 142 + color: var(--color-text-muted); 143 + flex-shrink: 0; 144 + width: 2.5rem; 145 + text-align: center; 146 + } 147 + 148 + .catalog-item-content { 149 + flex: 1; 150 + } 151 + 152 + .catalog-item h3 { 153 + color: var(--color-heading); 154 + margin-bottom: 0.5rem; 155 + } 156 + 157 + .catalog-item p { 158 + color: var(--color-text-muted); 159 + margin-bottom: 1rem; 160 + } 161 + 162 + .catalog-item a { 163 + display: inline-block; 164 + background: var(--color-success); 165 + color: white; 166 + padding: 0.5rem 1rem; 167 + border-radius: var(--radius-sm); 168 + text-decoration: none; 169 + } 170 + 171 + .catalog-item a:hover { 172 + background: var(--color-success-hover); 173 + } 174 + 175 + footer { 176 + margin-top: 2rem; 177 + padding-top: 1rem; 178 + border-top: 1px solid var(--color-border); 179 + text-align: center; 180 + color: var(--color-text-muted); 181 + font-size: 0.9rem; 182 + } 183 + 184 + .text-muted { 185 + color: var(--color-text-muted); 186 + } 187 + 188 + .text-sm { 189 + font-size: 0.9rem; 190 + } 191 + </style> 192 + </head> 193 + 194 + <body> 195 + <header> 196 + <h1 title="A Magazi digital catalog for @{{ creator_handle }}">Digital Catalog for @{{ creator_handle }}</h1> 197 + <nav> 198 + <a href="/">Home</a> 199 + {% if logged_in %} 200 + <a href="/logout">Logout</a> 201 + {% else %} 202 + <a href="/login">Login</a> 203 + {% endif %} 204 + </nav> 205 + </header> 206 + <main> 207 + {% block content %}{% endblock %} 208 + </main> 209 + <footer> 210 + Powered by <a href="https://tangled.org/ngerakines.me/magazi">Magazi</a> made by <a 211 + href="https://bsky.app/profile/ngerakines.me">@ngerakines.me</a> 212 + </footer> 213 + </body> 214 + 215 + </html>
+52
templates/home.html
··· 1 + {% extends "base.html" %} 2 + 3 + {% block title %}Digital Catalog{% endblock %} 4 + 5 + {% block content %} 6 + {% if login_required %} 7 + <div class="alert error"> 8 + You must be logged in to view that page. Please log in to continue. 9 + </div> 10 + {% endif %} 11 + 12 + {% if logged_out %} 13 + <div class="alert success"> 14 + You have been successfully logged out. 15 + </div> 16 + {% endif %} 17 + 18 + {% if logged_in %} 19 + <p style="margin: 0 0 1rem"> 20 + Welcome back, <strong>@{{ handle }}</strong>! 21 + </p> 22 + {% endif %} 23 + 24 + <p style="margin: 0 0 1rem"> 25 + This is a digital content catalog for @{{ creator_handle }}. Get access to exclusive content 26 + by becoming a supporter of <a href="https://atprotofans.com/support/{{ creator_did }}" target="_blank">@{{ 27 + creator_handle }} on atprotofans.com</a>. 28 + </p> 29 + 30 + {% if not logged_in %} 31 + <p style="margin: 0 0 1rem"> 32 + Already a supporter? <a href="/login">Log in with your ATProtocol identity</a> to access your content. 33 + </p> 34 + {% endif %} 35 + 36 + <h3 style="margin: 1rem 0 1rem">Available Content</h3> 37 + 38 + {% for item in catalog %} 39 + <div class="catalog-item"> 40 + <div class="catalog-item-icon"> 41 + <i class="{{ item.icon }}"></i> 42 + </div> 43 + <div class="catalog-item-content"> 44 + <h3>{{ item.name }}</h3> 45 + <p>{{ item.description }}</p> 46 + {% if item.entitled %} 47 + <a href="{{ item.download_url }}">Download</a> 48 + {% endif %} 49 + </div> 50 + </div> 51 + {% endfor %} 52 + {% endblock %}
+27
templates/login.html
··· 1 + {% extends "base.html" %} 2 + 3 + {% block title %}Login - Magazi{% endblock %} 4 + 5 + {% block content %} 6 + <h2>Login</h2> 7 + 8 + {% if error %} 9 + <div class="alert error"> 10 + {{ error }} 11 + </div> 12 + {% endif %} 13 + 14 + <p style="margin: 1rem 0;"> 15 + Enter your internet handle, DID, or PDS URL to log in. 16 + </p> 17 + 18 + <form method="POST" action="/login"> 19 + <label for="handle">Handle or DID</label> 20 + <input type="text" id="handle" name="handle" placeholder="@alice.bsky.social or did:plc:..." required> 21 + <button type="submit">Continue</button> 22 + </form> 23 + 24 + <p class="text-muted text-sm" style="margin-top: 2rem;"> 25 + Examples: <code>@ngerakines.me</code>, <code>ngerakines.me</code>, <code>did:plc:abc123</code>, or <code>https://bsky.social</code> 26 + </p> 27 + {% endblock %}