Microservice to bring 2FA to self hosted PDSes

Compare changes

Choose any two refs to compare.

+4
.dockerignore
··· 1 + target 2 + /target 3 + **/.idea 4 + .idea
+1497 -151
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "abnf" 7 + version = "0.13.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a" 10 + dependencies = [ 11 + "abnf-core", 12 + "nom 7.1.3", 13 + ] 14 + 15 + [[package]] 16 + name = "abnf-core" 17 + version = "0.5.0" 18 + source = "registry+https://github.com/rust-lang/crates.io-index" 19 + checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" 20 + dependencies = [ 21 + "nom 7.1.3", 22 + ] 23 + 24 + [[package]] 6 25 name = "addr2line" 7 26 version = "0.24.2" 8 27 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 39 58 ] 40 59 41 60 [[package]] 42 - name = "allocator-api2" 43 - version = "0.2.21" 61 + name = "aliasable" 62 + version = "0.1.3" 44 63 source = "registry+https://github.com/rust-lang/crates.io-index" 45 - checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 64 + checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 46 65 47 66 [[package]] 48 - name = "android-tzdata" 49 - version = "0.1.1" 67 + name = "allocator-api2" 68 + version = "0.2.21" 50 69 source = "registry+https://github.com/rust-lang/crates.io-index" 51 - checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 70 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 52 71 53 72 [[package]] 54 73 name = "android_system_properties" ··· 71 90 source = "registry+https://github.com/rust-lang/crates.io-index" 72 91 checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" 73 92 dependencies = [ 93 + "flate2", 74 94 "futures-core", 75 95 "memchr", 76 96 "pin-project-lite", ··· 87 107 dependencies = [ 88 108 "proc-macro2", 89 109 "quote", 90 - "syn", 110 + "syn 2.0.105", 91 111 ] 92 112 93 113 [[package]] ··· 112 132 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 113 133 114 134 [[package]] 135 + name = "aws-lc-rs" 136 + version = "1.13.3" 137 + source = "registry+https://github.com/rust-lang/crates.io-index" 138 + checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" 139 + dependencies = [ 140 + "aws-lc-sys", 141 + "untrusted 0.7.1", 142 + "zeroize", 143 + ] 144 + 145 + [[package]] 146 + name = "aws-lc-sys" 147 + version = "0.30.0" 148 + source = "registry+https://github.com/rust-lang/crates.io-index" 149 + checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" 150 + dependencies = [ 151 + "bindgen", 152 + "cc", 153 + "cmake", 154 + "dunce", 155 + "fs_extra", 156 + ] 157 + 158 + [[package]] 115 159 name = "axum" 116 160 version = "0.8.4" 117 161 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 174 218 dependencies = [ 175 219 "proc-macro2", 176 220 "quote", 177 - "syn", 221 + "syn 2.0.105", 178 222 ] 179 223 180 224 [[package]] ··· 205 249 ] 206 250 207 251 [[package]] 252 + name = "base-x" 253 + version = "0.2.11" 254 + source = "registry+https://github.com/rust-lang/crates.io-index" 255 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 256 + 257 + [[package]] 258 + name = "base16ct" 259 + version = "0.2.0" 260 + source = "registry+https://github.com/rust-lang/crates.io-index" 261 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 262 + 263 + [[package]] 264 + name = "base256emoji" 265 + version = "1.0.2" 266 + source = "registry+https://github.com/rust-lang/crates.io-index" 267 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 268 + dependencies = [ 269 + "const-str", 270 + "match-lookup", 271 + ] 272 + 273 + [[package]] 208 274 name = "base64" 209 275 version = "0.22.1" 210 276 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 217 283 checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" 218 284 219 285 [[package]] 286 + name = "bindgen" 287 + version = "0.69.5" 288 + source = "registry+https://github.com/rust-lang/crates.io-index" 289 + checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 290 + dependencies = [ 291 + "bitflags", 292 + "cexpr", 293 + "clang-sys", 294 + "itertools", 295 + "lazy_static", 296 + "lazycell", 297 + "log", 298 + "prettyplease", 299 + "proc-macro2", 300 + "quote", 301 + "regex", 302 + "rustc-hash 1.1.0", 303 + "shlex", 304 + "syn 2.0.105", 305 + "which", 306 + ] 307 + 308 + [[package]] 220 309 name = "bitflags" 221 310 version = "2.9.1" 222 311 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 235 324 ] 236 325 237 326 [[package]] 327 + name = "bon" 328 + version = "3.8.1" 329 + source = "registry+https://github.com/rust-lang/crates.io-index" 330 + checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1" 331 + dependencies = [ 332 + "bon-macros", 333 + "rustversion", 334 + ] 335 + 336 + [[package]] 337 + name = "bon-macros" 338 + version = "3.8.1" 339 + source = "registry+https://github.com/rust-lang/crates.io-index" 340 + checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645" 341 + dependencies = [ 342 + "darling 0.21.3", 343 + "ident_case", 344 + "prettyplease", 345 + "proc-macro2", 346 + "quote", 347 + "rustversion", 348 + "syn 2.0.105", 349 + ] 350 + 351 + [[package]] 352 + name = "borsh" 353 + version = "1.6.0" 354 + source = "registry+https://github.com/rust-lang/crates.io-index" 355 + checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" 356 + dependencies = [ 357 + "cfg_aliases", 358 + ] 359 + 360 + [[package]] 238 361 name = "bstr" 239 362 version = "1.12.0" 240 363 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 245 368 ] 246 369 247 370 [[package]] 371 + name = "btree-range-map" 372 + version = "0.7.2" 373 + source = "registry+https://github.com/rust-lang/crates.io-index" 374 + checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" 375 + dependencies = [ 376 + "btree-slab", 377 + "cc-traits", 378 + "range-traits", 379 + "serde", 380 + "slab", 381 + ] 382 + 383 + [[package]] 384 + name = "btree-slab" 385 + version = "0.6.1" 386 + source = "registry+https://github.com/rust-lang/crates.io-index" 387 + checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" 388 + dependencies = [ 389 + "cc-traits", 390 + "slab", 391 + "smallvec", 392 + ] 393 + 394 + [[package]] 248 395 name = "bumpalo" 249 396 version = "3.19.0" 250 397 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 261 408 version = "1.10.1" 262 409 source = "registry+https://github.com/rust-lang/crates.io-index" 263 410 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 411 + dependencies = [ 412 + "serde", 413 + ] 414 + 415 + [[package]] 416 + name = "cbor4ii" 417 + version = "0.2.14" 418 + source = "registry+https://github.com/rust-lang/crates.io-index" 419 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 420 + dependencies = [ 421 + "serde", 422 + ] 264 423 265 424 [[package]] 266 425 name = "cc" ··· 274 433 ] 275 434 276 435 [[package]] 436 + name = "cc-traits" 437 + version = "2.0.0" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" 440 + dependencies = [ 441 + "slab", 442 + ] 443 + 444 + [[package]] 445 + name = "cexpr" 446 + version = "0.6.0" 447 + source = "registry+https://github.com/rust-lang/crates.io-index" 448 + checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 449 + dependencies = [ 450 + "nom 7.1.3", 451 + ] 452 + 453 + [[package]] 277 454 name = "cfg-if" 278 455 version = "1.0.1" 279 456 source = "registry+https://github.com/rust-lang/crates.io-index" 280 457 checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 281 458 282 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]] 283 466 name = "chrono" 284 - version = "0.4.41" 467 + version = "0.4.42" 285 468 source = "registry+https://github.com/rust-lang/crates.io-index" 286 - checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 469 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 287 470 dependencies = [ 288 - "android-tzdata", 289 471 "iana-time-zone", 290 472 "js-sys", 291 473 "num-traits", 474 + "serde", 292 475 "wasm-bindgen", 293 - "windows-link", 476 + "windows-link 0.2.1", 294 477 ] 295 478 296 479 [[package]] ··· 331 514 ] 332 515 333 516 [[package]] 517 + name = "cid" 518 + version = "0.11.1" 519 + source = "registry+https://github.com/rust-lang/crates.io-index" 520 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 521 + dependencies = [ 522 + "core2", 523 + "multibase", 524 + "multihash", 525 + "serde", 526 + "serde_bytes", 527 + "unsigned-varint", 528 + ] 529 + 530 + [[package]] 334 531 name = "cipher" 335 532 version = "0.4.4" 336 533 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 341 538 ] 342 539 343 540 [[package]] 541 + name = "clang-sys" 542 + version = "1.8.1" 543 + source = "registry+https://github.com/rust-lang/crates.io-index" 544 + checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 545 + dependencies = [ 546 + "glob", 547 + "libc", 548 + "libloading", 549 + ] 550 + 551 + [[package]] 552 + name = "cmake" 553 + version = "0.1.54" 554 + source = "registry+https://github.com/rust-lang/crates.io-index" 555 + checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 556 + dependencies = [ 557 + "cc", 558 + ] 559 + 560 + [[package]] 344 561 name = "concurrent-queue" 345 562 version = "2.5.0" 346 563 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 356 573 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 357 574 358 575 [[package]] 576 + name = "const-str" 577 + version = "0.4.3" 578 + source = "registry+https://github.com/rust-lang/crates.io-index" 579 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 580 + 581 + [[package]] 359 582 name = "core-foundation" 360 583 version = "0.9.4" 361 584 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 372 595 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 373 596 374 597 [[package]] 598 + name = "core2" 599 + version = "0.4.0" 600 + source = "registry+https://github.com/rust-lang/crates.io-index" 601 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 602 + dependencies = [ 603 + "memchr", 604 + ] 605 + 606 + [[package]] 375 607 name = "cpufeatures" 376 608 version = "0.2.17" 377 609 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 396 628 checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 397 629 398 630 [[package]] 631 + name = "crc32fast" 632 + version = "1.5.0" 633 + source = "registry+https://github.com/rust-lang/crates.io-index" 634 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 635 + dependencies = [ 636 + "cfg-if", 637 + ] 638 + 639 + [[package]] 399 640 name = "crossbeam-queue" 400 641 version = "0.3.12" 401 642 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 417 658 checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 418 659 419 660 [[package]] 661 + name = "crypto-bigint" 662 + version = "0.5.5" 663 + source = "registry+https://github.com/rust-lang/crates.io-index" 664 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 665 + dependencies = [ 666 + "generic-array", 667 + "rand_core 0.6.4", 668 + "subtle", 669 + "zeroize", 670 + ] 671 + 672 + [[package]] 420 673 name = "crypto-common" 421 674 version = "0.1.6" 422 675 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 432 685 source = "registry+https://github.com/rust-lang/crates.io-index" 433 686 checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 434 687 dependencies = [ 435 - "darling_core", 436 - "darling_macro", 688 + "darling_core 0.20.11", 689 + "darling_macro 0.20.11", 690 + ] 691 + 692 + [[package]] 693 + name = "darling" 694 + version = "0.21.3" 695 + source = "registry+https://github.com/rust-lang/crates.io-index" 696 + checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 697 + dependencies = [ 698 + "darling_core 0.21.3", 699 + "darling_macro 0.21.3", 437 700 ] 438 701 439 702 [[package]] ··· 447 710 "proc-macro2", 448 711 "quote", 449 712 "strsim", 450 - "syn", 713 + "syn 2.0.105", 714 + ] 715 + 716 + [[package]] 717 + name = "darling_core" 718 + version = "0.21.3" 719 + source = "registry+https://github.com/rust-lang/crates.io-index" 720 + checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 721 + dependencies = [ 722 + "fnv", 723 + "ident_case", 724 + "proc-macro2", 725 + "quote", 726 + "strsim", 727 + "syn 2.0.105", 451 728 ] 452 729 453 730 [[package]] ··· 456 733 source = "registry+https://github.com/rust-lang/crates.io-index" 457 734 checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 458 735 dependencies = [ 459 - "darling_core", 736 + "darling_core 0.20.11", 460 737 "quote", 461 - "syn", 738 + "syn 2.0.105", 739 + ] 740 + 741 + [[package]] 742 + name = "darling_macro" 743 + version = "0.21.3" 744 + source = "registry+https://github.com/rust-lang/crates.io-index" 745 + checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 746 + dependencies = [ 747 + "darling_core 0.21.3", 748 + "quote", 749 + "syn 2.0.105", 462 750 ] 463 751 464 752 [[package]] ··· 476 764 ] 477 765 478 766 [[package]] 767 + name = "data-encoding" 768 + version = "2.9.0" 769 + source = "registry+https://github.com/rust-lang/crates.io-index" 770 + checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 771 + 772 + [[package]] 773 + name = "data-encoding-macro" 774 + version = "0.1.18" 775 + source = "registry+https://github.com/rust-lang/crates.io-index" 776 + checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" 777 + dependencies = [ 778 + "data-encoding", 779 + "data-encoding-macro-internal", 780 + ] 781 + 782 + [[package]] 783 + name = "data-encoding-macro-internal" 784 + version = "0.1.16" 785 + source = "registry+https://github.com/rust-lang/crates.io-index" 786 + checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 787 + dependencies = [ 788 + "data-encoding", 789 + "syn 2.0.105", 790 + ] 791 + 792 + [[package]] 479 793 name = "der" 480 794 version = "0.7.10" 481 795 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 487 801 ] 488 802 489 803 [[package]] 804 + name = "deranged" 805 + version = "0.5.5" 806 + source = "registry+https://github.com/rust-lang/crates.io-index" 807 + checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 808 + dependencies = [ 809 + "powerfmt", 810 + "serde_core", 811 + ] 812 + 813 + [[package]] 490 814 name = "derive_builder" 491 815 version = "0.20.2" 492 816 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 501 825 source = "registry+https://github.com/rust-lang/crates.io-index" 502 826 checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 503 827 dependencies = [ 504 - "darling", 828 + "darling 0.20.11", 505 829 "proc-macro2", 506 830 "quote", 507 - "syn", 831 + "syn 2.0.105", 508 832 ] 509 833 510 834 [[package]] ··· 514 838 checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 515 839 dependencies = [ 516 840 "derive_builder_core", 517 - "syn", 841 + "syn 2.0.105", 518 842 ] 519 843 520 844 [[package]] ··· 537 861 dependencies = [ 538 862 "proc-macro2", 539 863 "quote", 540 - "syn", 864 + "syn 2.0.105", 541 865 ] 542 866 543 867 [[package]] ··· 547 871 checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 548 872 549 873 [[package]] 874 + name = "dunce" 875 + version = "1.0.5" 876 + source = "registry+https://github.com/rust-lang/crates.io-index" 877 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 878 + 879 + [[package]] 880 + name = "dyn-clone" 881 + version = "1.0.20" 882 + source = "registry+https://github.com/rust-lang/crates.io-index" 883 + checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 884 + 885 + [[package]] 886 + name = "ecdsa" 887 + version = "0.16.9" 888 + source = "registry+https://github.com/rust-lang/crates.io-index" 889 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 890 + dependencies = [ 891 + "der", 892 + "digest", 893 + "elliptic-curve", 894 + "rfc6979", 895 + "signature", 896 + "spki", 897 + ] 898 + 899 + [[package]] 550 900 name = "either" 551 901 version = "1.15.0" 552 902 source = "registry+https://github.com/rust-lang/crates.io-index" 553 903 checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 554 904 dependencies = [ 555 905 "serde", 906 + ] 907 + 908 + [[package]] 909 + name = "elliptic-curve" 910 + version = "0.13.8" 911 + source = "registry+https://github.com/rust-lang/crates.io-index" 912 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 913 + dependencies = [ 914 + "base16ct", 915 + "crypto-bigint", 916 + "digest", 917 + "ff", 918 + "generic-array", 919 + "group", 920 + "pem-rfc7468", 921 + "pkcs8", 922 + "rand_core 0.6.4", 923 + "sec1", 924 + "subtle", 925 + "zeroize", 556 926 ] 557 927 558 928 [[package]] ··· 572 942 checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" 573 943 574 944 [[package]] 945 + name = "encoding_rs" 946 + version = "0.8.35" 947 + source = "registry+https://github.com/rust-lang/crates.io-index" 948 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 949 + dependencies = [ 950 + "cfg-if", 951 + ] 952 + 953 + [[package]] 575 954 name = "equivalent" 576 955 version = "1.0.2" 577 956 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 616 995 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 617 996 618 997 [[package]] 998 + name = "ff" 999 + version = "0.13.1" 1000 + source = "registry+https://github.com/rust-lang/crates.io-index" 1001 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 1002 + dependencies = [ 1003 + "rand_core 0.6.4", 1004 + "subtle", 1005 + ] 1006 + 1007 + [[package]] 1008 + name = "flate2" 1009 + version = "1.1.5" 1010 + source = "registry+https://github.com/rust-lang/crates.io-index" 1011 + checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 1012 + dependencies = [ 1013 + "crc32fast", 1014 + "miniz_oxide", 1015 + ] 1016 + 1017 + [[package]] 619 1018 name = "flume" 620 1019 version = "0.11.1" 621 1020 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 673 1072 ] 674 1073 675 1074 [[package]] 1075 + name = "fs_extra" 1076 + version = "1.3.0" 1077 + source = "registry+https://github.com/rust-lang/crates.io-index" 1078 + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 1079 + 1080 + [[package]] 676 1081 name = "futures-channel" 677 1082 version = "0.3.31" 678 1083 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 717 1122 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 718 1123 719 1124 [[package]] 1125 + name = "futures-macro" 1126 + version = "0.3.31" 1127 + source = "registry+https://github.com/rust-lang/crates.io-index" 1128 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1129 + dependencies = [ 1130 + "proc-macro2", 1131 + "quote", 1132 + "syn 2.0.105", 1133 + ] 1134 + 1135 + [[package]] 720 1136 name = "futures-sink" 721 1137 version = "0.3.31" 722 1138 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 742 1158 dependencies = [ 743 1159 "futures-core", 744 1160 "futures-io", 1161 + "futures-macro", 745 1162 "futures-sink", 746 1163 "futures-task", 747 1164 "memchr", ··· 758 1175 dependencies = [ 759 1176 "typenum", 760 1177 "version_check", 1178 + "zeroize", 761 1179 ] 762 1180 763 1181 [[package]] ··· 767 1185 checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 768 1186 dependencies = [ 769 1187 "cfg-if", 1188 + "js-sys", 770 1189 "libc", 771 - "wasi 0.11.1+wasi-snapshot-preview1", 1190 + "wasi", 1191 + "wasm-bindgen", 772 1192 ] 773 1193 774 1194 [[package]] 775 1195 name = "getrandom" 776 - version = "0.3.3" 1196 + version = "0.3.4" 777 1197 source = "registry+https://github.com/rust-lang/crates.io-index" 778 - checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 1198 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 779 1199 dependencies = [ 780 1200 "cfg-if", 781 1201 "js-sys", 782 1202 "libc", 783 1203 "r-efi", 784 - "wasi 0.14.2+wasi-0.2.4", 1204 + "wasip2", 785 1205 "wasm-bindgen", 786 1206 ] 787 1207 ··· 792 1212 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 793 1213 794 1214 [[package]] 1215 + name = "glob" 1216 + version = "0.3.3" 1217 + source = "registry+https://github.com/rust-lang/crates.io-index" 1218 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1219 + 1220 + [[package]] 795 1221 name = "globset" 796 1222 version = "0.4.16" 797 1223 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 800 1226 "aho-corasick", 801 1227 "bstr", 802 1228 "log", 803 - "regex-automata 0.4.9", 1229 + "regex-automata 0.4.13", 804 1230 "regex-syntax 0.8.5", 805 1231 ] 806 1232 ··· 815 1241 "futures-sink", 816 1242 "futures-timer", 817 1243 "futures-util", 818 - "getrandom 0.3.3", 1244 + "getrandom 0.3.4", 819 1245 "hashbrown 0.15.5", 820 1246 "nonzero_ext", 821 1247 "parking_lot", ··· 828 1254 ] 829 1255 830 1256 [[package]] 1257 + name = "group" 1258 + version = "0.13.0" 1259 + source = "registry+https://github.com/rust-lang/crates.io-index" 1260 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1261 + dependencies = [ 1262 + "ff", 1263 + "rand_core 0.6.4", 1264 + "subtle", 1265 + ] 1266 + 1267 + [[package]] 831 1268 name = "h2" 832 1269 version = "0.4.12" 833 1270 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 839 1276 "futures-core", 840 1277 "futures-sink", 841 1278 "http", 842 - "indexmap", 1279 + "indexmap 2.10.0", 843 1280 "slab", 844 1281 "tokio", 845 1282 "tokio-util", ··· 875 1312 876 1313 [[package]] 877 1314 name = "hashbrown" 1315 + version = "0.12.3" 1316 + source = "registry+https://github.com/rust-lang/crates.io-index" 1317 + checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 1318 + 1319 + [[package]] 1320 + name = "hashbrown" 878 1321 version = "0.14.5" 879 1322 source = "registry+https://github.com/rust-lang/crates.io-index" 880 1323 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" ··· 905 1348 906 1349 [[package]] 907 1350 name = "heck" 1351 + version = "0.4.1" 1352 + source = "registry+https://github.com/rust-lang/crates.io-index" 1353 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1354 + 1355 + [[package]] 1356 + name = "heck" 908 1357 version = "0.5.0" 909 1358 source = "registry+https://github.com/rust-lang/crates.io-index" 910 1359 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" ··· 914 1363 version = "0.4.3" 915 1364 source = "registry+https://github.com/rust-lang/crates.io-index" 916 1365 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1366 + 1367 + [[package]] 1368 + name = "hex_fmt" 1369 + version = "0.3.0" 1370 + source = "registry+https://github.com/rust-lang/crates.io-index" 1371 + checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" 917 1372 918 1373 [[package]] 919 1374 name = "hkdf" ··· 943 1398 ] 944 1399 945 1400 [[package]] 946 - name = "hostname" 947 - version = "0.4.1" 1401 + name = "html-escape" 1402 + version = "0.2.13" 948 1403 source = "registry+https://github.com/rust-lang/crates.io-index" 949 - checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" 1404 + checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" 950 1405 dependencies = [ 951 - "cfg-if", 952 - "libc", 953 - "windows-link", 1406 + "utf8-width", 954 1407 ] 955 1408 956 1409 [[package]] ··· 1021 1474 ] 1022 1475 1023 1476 [[package]] 1477 + name = "hyper-rustls" 1478 + version = "0.27.7" 1479 + source = "registry+https://github.com/rust-lang/crates.io-index" 1480 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1481 + dependencies = [ 1482 + "http", 1483 + "hyper", 1484 + "hyper-util", 1485 + "rustls", 1486 + "rustls-pki-types", 1487 + "tokio", 1488 + "tokio-rustls", 1489 + "tower-service", 1490 + "webpki-roots 1.0.2", 1491 + ] 1492 + 1493 + [[package]] 1024 1494 name = "hyper-timeout" 1025 1495 version = "0.5.2" 1026 1496 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1039 1509 source = "registry+https://github.com/rust-lang/crates.io-index" 1040 1510 checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 1041 1511 dependencies = [ 1512 + "base64", 1042 1513 "bytes", 1043 1514 "futures-channel", 1044 1515 "futures-core", ··· 1046 1517 "http", 1047 1518 "http-body", 1048 1519 "hyper", 1520 + "ipnet", 1049 1521 "libc", 1522 + "percent-encoding", 1050 1523 "pin-project-lite", 1051 1524 "socket2", 1525 + "system-configuration", 1052 1526 "tokio", 1053 1527 "tower-service", 1054 1528 "tracing", 1529 + "windows-registry", 1055 1530 ] 1056 1531 1057 1532 [[package]] ··· 1193 1668 1194 1669 [[package]] 1195 1670 name = "indexmap" 1671 + version = "1.9.3" 1672 + source = "registry+https://github.com/rust-lang/crates.io-index" 1673 + checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 1674 + dependencies = [ 1675 + "autocfg", 1676 + "hashbrown 0.12.3", 1677 + "serde", 1678 + ] 1679 + 1680 + [[package]] 1681 + name = "indexmap" 1196 1682 version = "2.10.0" 1197 1683 source = "registry+https://github.com/rust-lang/crates.io-index" 1198 1684 checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 1199 1685 dependencies = [ 1200 1686 "equivalent", 1201 1687 "hashbrown 0.15.5", 1688 + "serde", 1689 + ] 1690 + 1691 + [[package]] 1692 + name = "indoc" 1693 + version = "2.0.7" 1694 + source = "registry+https://github.com/rust-lang/crates.io-index" 1695 + checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 1696 + dependencies = [ 1697 + "rustversion", 1202 1698 ] 1203 1699 1204 1700 [[package]] ··· 1211 1707 ] 1212 1708 1213 1709 [[package]] 1710 + name = "inventory" 1711 + version = "0.3.21" 1712 + source = "registry+https://github.com/rust-lang/crates.io-index" 1713 + checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" 1714 + dependencies = [ 1715 + "rustversion", 1716 + ] 1717 + 1718 + [[package]] 1214 1719 name = "io-uring" 1215 1720 version = "0.7.9" 1216 1721 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1222 1727 ] 1223 1728 1224 1729 [[package]] 1730 + name = "ipld-core" 1731 + version = "0.4.2" 1732 + source = "registry+https://github.com/rust-lang/crates.io-index" 1733 + checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 1734 + dependencies = [ 1735 + "cid", 1736 + "serde", 1737 + "serde_bytes", 1738 + ] 1739 + 1740 + [[package]] 1741 + name = "ipnet" 1742 + version = "2.11.0" 1743 + source = "registry+https://github.com/rust-lang/crates.io-index" 1744 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1745 + 1746 + [[package]] 1747 + name = "iri-string" 1748 + version = "0.7.9" 1749 + source = "registry+https://github.com/rust-lang/crates.io-index" 1750 + checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1751 + dependencies = [ 1752 + "memchr", 1753 + "serde", 1754 + ] 1755 + 1756 + [[package]] 1757 + name = "itertools" 1758 + version = "0.12.1" 1759 + source = "registry+https://github.com/rust-lang/crates.io-index" 1760 + checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1761 + dependencies = [ 1762 + "either", 1763 + ] 1764 + 1765 + [[package]] 1225 1766 name = "itoa" 1226 1767 version = "1.0.15" 1227 1768 source = "registry+https://github.com/rust-lang/crates.io-index" 1228 1769 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1229 1770 1230 1771 [[package]] 1772 + name = "jacquard-api" 1773 + version = "0.9.2" 1774 + source = "registry+https://github.com/rust-lang/crates.io-index" 1775 + checksum = "bbbfd6e2b10fa1731f4d4e40c8f791956b0d4f804fb3efef891afec903f20597" 1776 + dependencies = [ 1777 + "bon", 1778 + "bytes", 1779 + "jacquard-common", 1780 + "jacquard-derive", 1781 + "jacquard-lexicon", 1782 + "miette", 1783 + "rustversion", 1784 + "serde", 1785 + "serde_ipld_dagcbor", 1786 + "thiserror 2.0.14", 1787 + "unicode-segmentation", 1788 + ] 1789 + 1790 + [[package]] 1791 + name = "jacquard-common" 1792 + version = "0.9.2" 1793 + source = "registry+https://github.com/rust-lang/crates.io-index" 1794 + checksum = "df86cb117d9f1c2b0251ba67c3f0e3f963fd22abc6cf8de0e02a7fc846c288ca" 1795 + dependencies = [ 1796 + "base64", 1797 + "bon", 1798 + "bytes", 1799 + "chrono", 1800 + "cid", 1801 + "getrandom 0.2.16", 1802 + "getrandom 0.3.4", 1803 + "http", 1804 + "ipld-core", 1805 + "k256", 1806 + "langtag", 1807 + "miette", 1808 + "multibase", 1809 + "multihash", 1810 + "ouroboros", 1811 + "p256", 1812 + "rand 0.9.2", 1813 + "regex", 1814 + "regex-lite", 1815 + "reqwest", 1816 + "serde", 1817 + "serde_html_form", 1818 + "serde_ipld_dagcbor", 1819 + "serde_json", 1820 + "signature", 1821 + "smol_str", 1822 + "thiserror 2.0.14", 1823 + "tokio", 1824 + "tokio-util", 1825 + "trait-variant", 1826 + "url", 1827 + ] 1828 + 1829 + [[package]] 1830 + name = "jacquard-derive" 1831 + version = "0.9.2" 1832 + source = "registry+https://github.com/rust-lang/crates.io-index" 1833 + checksum = "42ca61a69dc7aa8fb2d7163416514ff7df5d79f2e8b22e269f4610afa85572fe" 1834 + dependencies = [ 1835 + "heck 0.5.0", 1836 + "jacquard-lexicon", 1837 + "proc-macro2", 1838 + "quote", 1839 + "syn 2.0.105", 1840 + ] 1841 + 1842 + [[package]] 1843 + name = "jacquard-identity" 1844 + version = "0.9.2" 1845 + source = "registry+https://github.com/rust-lang/crates.io-index" 1846 + checksum = "1ef714cacebfca486558a9f8e205daf466bfba0466c4d0c450fd6d0252400a53" 1847 + dependencies = [ 1848 + "bon", 1849 + "bytes", 1850 + "http", 1851 + "jacquard-api", 1852 + "jacquard-common", 1853 + "jacquard-lexicon", 1854 + "miette", 1855 + "percent-encoding", 1856 + "reqwest", 1857 + "serde", 1858 + "serde_html_form", 1859 + "serde_json", 1860 + "thiserror 2.0.14", 1861 + "tokio", 1862 + "trait-variant", 1863 + "url", 1864 + "urlencoding", 1865 + ] 1866 + 1867 + [[package]] 1868 + name = "jacquard-lexicon" 1869 + version = "0.9.2" 1870 + source = "registry+https://github.com/rust-lang/crates.io-index" 1871 + checksum = "de87f2c938faea1b1f1b32d5b9e0c870e7b5bb5efbf96e3692ae2d8f6b2beb7a" 1872 + dependencies = [ 1873 + "cid", 1874 + "dashmap", 1875 + "heck 0.5.0", 1876 + "inventory", 1877 + "jacquard-common", 1878 + "miette", 1879 + "multihash", 1880 + "prettyplease", 1881 + "proc-macro2", 1882 + "quote", 1883 + "serde", 1884 + "serde_ipld_dagcbor", 1885 + "serde_json", 1886 + "serde_repr", 1887 + "serde_with", 1888 + "sha2", 1889 + "syn 2.0.105", 1890 + "thiserror 2.0.14", 1891 + "unicode-segmentation", 1892 + ] 1893 + 1894 + [[package]] 1231 1895 name = "jobserver" 1232 1896 version = "0.1.33" 1233 1897 source = "registry+https://github.com/rust-lang/crates.io-index" 1234 1898 checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1235 1899 dependencies = [ 1236 - "getrandom 0.3.3", 1900 + "getrandom 0.3.4", 1237 1901 "libc", 1238 1902 ] 1239 1903 1240 1904 [[package]] 1905 + name = "josekit" 1906 + version = "0.10.3" 1907 + source = "registry+https://github.com/rust-lang/crates.io-index" 1908 + checksum = "a808e078330e6af222eb0044b71d4b1ff981bfef43e7bc8133a88234e0c86a0c" 1909 + dependencies = [ 1910 + "anyhow", 1911 + "base64", 1912 + "flate2", 1913 + "openssl", 1914 + "regex", 1915 + "serde", 1916 + "serde_json", 1917 + "thiserror 2.0.14", 1918 + "time", 1919 + ] 1920 + 1921 + [[package]] 1241 1922 name = "js-sys" 1242 1923 version = "0.3.77" 1243 1924 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1270 1951 ] 1271 1952 1272 1953 [[package]] 1954 + name = "k256" 1955 + version = "0.13.4" 1956 + source = "registry+https://github.com/rust-lang/crates.io-index" 1957 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 1958 + dependencies = [ 1959 + "cfg-if", 1960 + "ecdsa", 1961 + "elliptic-curve", 1962 + "sha2", 1963 + ] 1964 + 1965 + [[package]] 1966 + name = "langtag" 1967 + version = "0.4.0" 1968 + source = "registry+https://github.com/rust-lang/crates.io-index" 1969 + checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600" 1970 + dependencies = [ 1971 + "serde", 1972 + "static-regular-grammar", 1973 + "thiserror 1.0.69", 1974 + ] 1975 + 1976 + [[package]] 1273 1977 name = "lazy_static" 1274 1978 version = "1.5.0" 1275 1979 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1277 1981 dependencies = [ 1278 1982 "spin", 1279 1983 ] 1984 + 1985 + [[package]] 1986 + name = "lazycell" 1987 + version = "1.3.0" 1988 + source = "registry+https://github.com/rust-lang/crates.io-index" 1989 + checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1280 1990 1281 1991 [[package]] 1282 1992 name = "lettre" ··· 1292 2002 "fastrand", 1293 2003 "futures-io", 1294 2004 "futures-util", 1295 - "hostname", 1296 2005 "httpdate", 1297 2006 "idna", 1298 2007 "mime", 1299 - "native-tls", 1300 - "nom", 2008 + "nom 8.0.0", 1301 2009 "percent-encoding", 1302 2010 "quoted_printable", 2011 + "rustls", 1303 2012 "socket2", 1304 2013 "tokio", 1305 - "tokio-native-tls", 2014 + "tokio-rustls", 1306 2015 "url", 2016 + "webpki-roots 1.0.2", 1307 2017 ] 1308 2018 1309 2019 [[package]] ··· 1311 2021 version = "0.2.175" 1312 2022 source = "registry+https://github.com/rust-lang/crates.io-index" 1313 2023 checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 2024 + 2025 + [[package]] 2026 + name = "libloading" 2027 + version = "0.8.8" 2028 + source = "registry+https://github.com/rust-lang/crates.io-index" 2029 + checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 2030 + dependencies = [ 2031 + "cfg-if", 2032 + "windows-targets 0.52.6", 2033 + ] 1314 2034 1315 2035 [[package]] 1316 2036 name = "libm" ··· 1342 2062 1343 2063 [[package]] 1344 2064 name = "linux-raw-sys" 1345 - version = "0.9.4" 2065 + version = "0.4.15" 1346 2066 source = "registry+https://github.com/rust-lang/crates.io-index" 1347 - checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 2067 + checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1348 2068 1349 2069 [[package]] 1350 2070 name = "litemap" ··· 1369 2089 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1370 2090 1371 2091 [[package]] 2092 + name = "lru-slab" 2093 + version = "0.1.2" 2094 + source = "registry+https://github.com/rust-lang/crates.io-index" 2095 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 2096 + 2097 + [[package]] 2098 + name = "match-lookup" 2099 + version = "0.1.1" 2100 + source = "registry+https://github.com/rust-lang/crates.io-index" 2101 + checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" 2102 + dependencies = [ 2103 + "proc-macro2", 2104 + "quote", 2105 + "syn 1.0.109", 2106 + ] 2107 + 2108 + [[package]] 1372 2109 name = "matchers" 1373 2110 version = "0.1.0" 1374 2111 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1400 2137 checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 1401 2138 1402 2139 [[package]] 2140 + name = "miette" 2141 + version = "7.6.0" 2142 + source = "registry+https://github.com/rust-lang/crates.io-index" 2143 + checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" 2144 + dependencies = [ 2145 + "cfg-if", 2146 + "miette-derive", 2147 + "unicode-width", 2148 + ] 2149 + 2150 + [[package]] 2151 + name = "miette-derive" 2152 + version = "7.6.0" 2153 + source = "registry+https://github.com/rust-lang/crates.io-index" 2154 + checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" 2155 + dependencies = [ 2156 + "proc-macro2", 2157 + "quote", 2158 + "syn 2.0.105", 2159 + ] 2160 + 2161 + [[package]] 1403 2162 name = "mime" 1404 2163 version = "0.3.17" 1405 2164 source = "registry+https://github.com/rust-lang/crates.io-index" 1406 2165 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1407 2166 1408 2167 [[package]] 2168 + name = "minimal-lexical" 2169 + version = "0.2.1" 2170 + source = "registry+https://github.com/rust-lang/crates.io-index" 2171 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 2172 + 2173 + [[package]] 1409 2174 name = "miniz_oxide" 1410 2175 version = "0.8.9" 1411 2176 source = "registry+https://github.com/rust-lang/crates.io-index" 1412 2177 checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1413 2178 dependencies = [ 1414 2179 "adler2", 2180 + "simd-adler32", 1415 2181 ] 1416 2182 1417 2183 [[package]] ··· 1421 2187 checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 1422 2188 dependencies = [ 1423 2189 "libc", 1424 - "wasi 0.11.1+wasi-snapshot-preview1", 2190 + "wasi", 1425 2191 "windows-sys 0.59.0", 1426 2192 ] 1427 2193 1428 2194 [[package]] 1429 - name = "native-tls" 1430 - version = "0.2.14" 2195 + name = "multibase" 2196 + version = "0.9.2" 2197 + source = "registry+https://github.com/rust-lang/crates.io-index" 2198 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 2199 + dependencies = [ 2200 + "base-x", 2201 + "base256emoji", 2202 + "data-encoding", 2203 + "data-encoding-macro", 2204 + ] 2205 + 2206 + [[package]] 2207 + name = "multihash" 2208 + version = "0.19.3" 2209 + source = "registry+https://github.com/rust-lang/crates.io-index" 2210 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 2211 + dependencies = [ 2212 + "core2", 2213 + "serde", 2214 + "unsigned-varint", 2215 + ] 2216 + 2217 + [[package]] 2218 + name = "nom" 2219 + version = "7.1.3" 1431 2220 source = "registry+https://github.com/rust-lang/crates.io-index" 1432 - checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 2221 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1433 2222 dependencies = [ 1434 - "libc", 1435 - "log", 1436 - "openssl", 1437 - "openssl-probe", 1438 - "openssl-sys", 1439 - "schannel", 1440 - "security-framework", 1441 - "security-framework-sys", 1442 - "tempfile", 2223 + "memchr", 2224 + "minimal-lexical", 1443 2225 ] 1444 2226 1445 2227 [[package]] ··· 1489 2271 "smallvec", 1490 2272 "zeroize", 1491 2273 ] 2274 + 2275 + [[package]] 2276 + name = "num-conv" 2277 + version = "0.1.0" 2278 + source = "registry+https://github.com/rust-lang/crates.io-index" 2279 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1492 2280 1493 2281 [[package]] 1494 2282 name = "num-integer" ··· 1552 2340 1553 2341 [[package]] 1554 2342 name = "openssl" 1555 - version = "0.10.73" 2343 + version = "0.10.75" 1556 2344 source = "registry+https://github.com/rust-lang/crates.io-index" 1557 - checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 2345 + checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" 1558 2346 dependencies = [ 1559 2347 "bitflags", 1560 2348 "cfg-if", ··· 1573 2361 dependencies = [ 1574 2362 "proc-macro2", 1575 2363 "quote", 1576 - "syn", 2364 + "syn 2.0.105", 1577 2365 ] 1578 2366 1579 2367 [[package]] 1580 - name = "openssl-probe" 1581 - version = "0.1.6" 1582 - source = "registry+https://github.com/rust-lang/crates.io-index" 1583 - checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1584 - 1585 - [[package]] 1586 2368 name = "openssl-sys" 1587 - version = "0.9.109" 2369 + version = "0.9.111" 1588 2370 source = "registry+https://github.com/rust-lang/crates.io-index" 1589 - checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 2371 + checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" 1590 2372 dependencies = [ 1591 2373 "cc", 1592 2374 "libc", ··· 1595 2377 ] 1596 2378 1597 2379 [[package]] 2380 + name = "ouroboros" 2381 + version = "0.18.5" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" 2384 + dependencies = [ 2385 + "aliasable", 2386 + "ouroboros_macro", 2387 + "static_assertions", 2388 + ] 2389 + 2390 + [[package]] 2391 + name = "ouroboros_macro" 2392 + version = "0.18.5" 2393 + source = "registry+https://github.com/rust-lang/crates.io-index" 2394 + checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" 2395 + dependencies = [ 2396 + "heck 0.4.1", 2397 + "proc-macro2", 2398 + "proc-macro2-diagnostics", 2399 + "quote", 2400 + "syn 2.0.105", 2401 + ] 2402 + 2403 + [[package]] 1598 2404 name = "overload" 1599 2405 version = "0.1.1" 1600 2406 source = "registry+https://github.com/rust-lang/crates.io-index" 1601 2407 checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 2408 + 2409 + [[package]] 2410 + name = "p256" 2411 + version = "0.13.2" 2412 + source = "registry+https://github.com/rust-lang/crates.io-index" 2413 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 2414 + dependencies = [ 2415 + "ecdsa", 2416 + "elliptic-curve", 2417 + "primeorder", 2418 + "sha2", 2419 + ] 1602 2420 1603 2421 [[package]] 1604 2422 name = "parking" ··· 1652 2470 1653 2471 [[package]] 1654 2472 name = "pds_gatekeeper" 1655 - version = "0.1.0" 2473 + version = "0.1.2" 1656 2474 dependencies = [ 1657 2475 "anyhow", 2476 + "aws-lc-rs", 1658 2477 "axum", 1659 2478 "axum-template", 1660 2479 "chrono", 1661 2480 "dotenvy", 1662 2481 "handlebars", 1663 2482 "hex", 2483 + "html-escape", 1664 2484 "hyper-util", 2485 + "jacquard-common", 2486 + "jacquard-identity", 2487 + "josekit", 1665 2488 "jwt-compact", 1666 2489 "lettre", 2490 + "multibase", 1667 2491 "rand 0.9.2", 2492 + "reqwest", 1668 2493 "rust-embed", 2494 + "rustls", 1669 2495 "scrypt", 1670 2496 "serde", 1671 2497 "serde_json", ··· 1676 2502 "tower_governor", 1677 2503 "tracing", 1678 2504 "tracing-subscriber", 2505 + "urlencoding", 1679 2506 ] 1680 2507 1681 2508 [[package]] ··· 1724 2551 "pest_meta", 1725 2552 "proc-macro2", 1726 2553 "quote", 1727 - "syn", 2554 + "syn 2.0.105", 1728 2555 ] 1729 2556 1730 2557 [[package]] ··· 1754 2581 dependencies = [ 1755 2582 "proc-macro2", 1756 2583 "quote", 1757 - "syn", 2584 + "syn 2.0.105", 1758 2585 ] 1759 2586 1760 2587 [[package]] ··· 1812 2639 ] 1813 2640 1814 2641 [[package]] 2642 + name = "powerfmt" 2643 + version = "0.2.0" 2644 + source = "registry+https://github.com/rust-lang/crates.io-index" 2645 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 2646 + 2647 + [[package]] 1815 2648 name = "ppv-lite86" 1816 2649 version = "0.2.21" 1817 2650 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1821 2654 ] 1822 2655 1823 2656 [[package]] 2657 + name = "prettyplease" 2658 + version = "0.2.35" 2659 + source = "registry+https://github.com/rust-lang/crates.io-index" 2660 + checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" 2661 + dependencies = [ 2662 + "proc-macro2", 2663 + "syn 2.0.105", 2664 + ] 2665 + 2666 + [[package]] 2667 + name = "primeorder" 2668 + version = "0.13.6" 2669 + source = "registry+https://github.com/rust-lang/crates.io-index" 2670 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 2671 + dependencies = [ 2672 + "elliptic-curve", 2673 + ] 2674 + 2675 + [[package]] 2676 + name = "proc-macro-error" 2677 + version = "1.0.4" 2678 + source = "registry+https://github.com/rust-lang/crates.io-index" 2679 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 2680 + dependencies = [ 2681 + "proc-macro-error-attr", 2682 + "proc-macro2", 2683 + "quote", 2684 + "syn 1.0.109", 2685 + "version_check", 2686 + ] 2687 + 2688 + [[package]] 2689 + name = "proc-macro-error-attr" 2690 + version = "1.0.4" 2691 + source = "registry+https://github.com/rust-lang/crates.io-index" 2692 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 2693 + dependencies = [ 2694 + "proc-macro2", 2695 + "quote", 2696 + "version_check", 2697 + ] 2698 + 2699 + [[package]] 1824 2700 name = "proc-macro2" 1825 2701 version = "1.0.97" 1826 2702 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1830 2706 ] 1831 2707 1832 2708 [[package]] 2709 + name = "proc-macro2-diagnostics" 2710 + version = "0.10.1" 2711 + source = "registry+https://github.com/rust-lang/crates.io-index" 2712 + checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" 2713 + dependencies = [ 2714 + "proc-macro2", 2715 + "quote", 2716 + "syn 2.0.105", 2717 + "version_check", 2718 + "yansi", 2719 + ] 2720 + 2721 + [[package]] 1833 2722 name = "psm" 1834 2723 version = "0.1.26" 1835 2724 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1848 2737 "libc", 1849 2738 "once_cell", 1850 2739 "raw-cpuid", 1851 - "wasi 0.11.1+wasi-snapshot-preview1", 2740 + "wasi", 1852 2741 "web-sys", 1853 2742 "winapi", 1854 2743 ] 1855 2744 1856 2745 [[package]] 2746 + name = "quinn" 2747 + version = "0.11.9" 2748 + source = "registry+https://github.com/rust-lang/crates.io-index" 2749 + checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 2750 + dependencies = [ 2751 + "bytes", 2752 + "cfg_aliases", 2753 + "pin-project-lite", 2754 + "quinn-proto", 2755 + "quinn-udp", 2756 + "rustc-hash 2.1.1", 2757 + "rustls", 2758 + "socket2", 2759 + "thiserror 2.0.14", 2760 + "tokio", 2761 + "tracing", 2762 + "web-time", 2763 + ] 2764 + 2765 + [[package]] 2766 + name = "quinn-proto" 2767 + version = "0.11.13" 2768 + source = "registry+https://github.com/rust-lang/crates.io-index" 2769 + checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 2770 + dependencies = [ 2771 + "bytes", 2772 + "getrandom 0.3.4", 2773 + "lru-slab", 2774 + "rand 0.9.2", 2775 + "ring", 2776 + "rustc-hash 2.1.1", 2777 + "rustls", 2778 + "rustls-pki-types", 2779 + "slab", 2780 + "thiserror 2.0.14", 2781 + "tinyvec", 2782 + "tracing", 2783 + "web-time", 2784 + ] 2785 + 2786 + [[package]] 2787 + name = "quinn-udp" 2788 + version = "0.5.14" 2789 + source = "registry+https://github.com/rust-lang/crates.io-index" 2790 + checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 2791 + dependencies = [ 2792 + "cfg_aliases", 2793 + "libc", 2794 + "once_cell", 2795 + "socket2", 2796 + "tracing", 2797 + "windows-sys 0.59.0", 2798 + ] 2799 + 2800 + [[package]] 1857 2801 name = "quote" 1858 2802 version = "1.0.40" 1859 2803 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1930 2874 source = "registry+https://github.com/rust-lang/crates.io-index" 1931 2875 checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1932 2876 dependencies = [ 1933 - "getrandom 0.3.3", 2877 + "getrandom 0.3.4", 1934 2878 ] 2879 + 2880 + [[package]] 2881 + name = "range-traits" 2882 + version = "0.3.2" 2883 + source = "registry+https://github.com/rust-lang/crates.io-index" 2884 + checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 1935 2885 1936 2886 [[package]] 1937 2887 name = "raw-cpuid" ··· 1952 2902 ] 1953 2903 1954 2904 [[package]] 2905 + name = "ref-cast" 2906 + version = "1.0.25" 2907 + source = "registry+https://github.com/rust-lang/crates.io-index" 2908 + checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" 2909 + dependencies = [ 2910 + "ref-cast-impl", 2911 + ] 2912 + 2913 + [[package]] 2914 + name = "ref-cast-impl" 2915 + version = "1.0.25" 2916 + source = "registry+https://github.com/rust-lang/crates.io-index" 2917 + checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" 2918 + dependencies = [ 2919 + "proc-macro2", 2920 + "quote", 2921 + "syn 2.0.105", 2922 + ] 2923 + 2924 + [[package]] 1955 2925 name = "regex" 1956 - version = "1.11.1" 2926 + version = "1.12.2" 1957 2927 source = "registry+https://github.com/rust-lang/crates.io-index" 1958 - checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 2928 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1959 2929 dependencies = [ 1960 2930 "aho-corasick", 1961 2931 "memchr", 1962 - "regex-automata 0.4.9", 2932 + "regex-automata 0.4.13", 1963 2933 "regex-syntax 0.8.5", 1964 2934 ] 1965 2935 ··· 1974 2944 1975 2945 [[package]] 1976 2946 name = "regex-automata" 1977 - version = "0.4.9" 2947 + version = "0.4.13" 1978 2948 source = "registry+https://github.com/rust-lang/crates.io-index" 1979 - checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 2949 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1980 2950 dependencies = [ 1981 2951 "aho-corasick", 1982 2952 "memchr", ··· 1984 2954 ] 1985 2955 1986 2956 [[package]] 2957 + name = "regex-lite" 2958 + version = "0.1.8" 2959 + source = "registry+https://github.com/rust-lang/crates.io-index" 2960 + checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 2961 + 2962 + [[package]] 1987 2963 name = "regex-syntax" 1988 2964 version = "0.6.29" 1989 2965 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1996 2972 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1997 2973 1998 2974 [[package]] 2975 + name = "reqwest" 2976 + version = "0.12.24" 2977 + source = "registry+https://github.com/rust-lang/crates.io-index" 2978 + checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 2979 + dependencies = [ 2980 + "async-compression", 2981 + "base64", 2982 + "bytes", 2983 + "encoding_rs", 2984 + "futures-core", 2985 + "futures-util", 2986 + "h2", 2987 + "http", 2988 + "http-body", 2989 + "http-body-util", 2990 + "hyper", 2991 + "hyper-rustls", 2992 + "hyper-util", 2993 + "js-sys", 2994 + "log", 2995 + "mime", 2996 + "percent-encoding", 2997 + "pin-project-lite", 2998 + "quinn", 2999 + "rustls", 3000 + "rustls-pki-types", 3001 + "serde", 3002 + "serde_json", 3003 + "serde_urlencoded", 3004 + "sync_wrapper", 3005 + "tokio", 3006 + "tokio-rustls", 3007 + "tokio-util", 3008 + "tower", 3009 + "tower-http", 3010 + "tower-service", 3011 + "url", 3012 + "wasm-bindgen", 3013 + "wasm-bindgen-futures", 3014 + "wasm-streams", 3015 + "web-sys", 3016 + "webpki-roots 1.0.2", 3017 + ] 3018 + 3019 + [[package]] 3020 + name = "rfc6979" 3021 + version = "0.4.0" 3022 + source = "registry+https://github.com/rust-lang/crates.io-index" 3023 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 3024 + dependencies = [ 3025 + "hmac", 3026 + "subtle", 3027 + ] 3028 + 3029 + [[package]] 1999 3030 name = "ring" 2000 3031 version = "0.17.14" 2001 3032 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2005 3036 "cfg-if", 2006 3037 "getrandom 0.2.16", 2007 3038 "libc", 2008 - "untrusted", 3039 + "untrusted 0.9.0", 2009 3040 "windows-sys 0.52.0", 2010 3041 ] 2011 3042 ··· 2049 3080 "proc-macro2", 2050 3081 "quote", 2051 3082 "rust-embed-utils", 2052 - "syn", 3083 + "syn 2.0.105", 2053 3084 "walkdir", 2054 3085 ] 2055 3086 ··· 2071 3102 checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 2072 3103 2073 3104 [[package]] 3105 + name = "rustc-hash" 3106 + version = "1.1.0" 3107 + source = "registry+https://github.com/rust-lang/crates.io-index" 3108 + checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 3109 + 3110 + [[package]] 3111 + name = "rustc-hash" 3112 + version = "2.1.1" 3113 + source = "registry+https://github.com/rust-lang/crates.io-index" 3114 + checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 3115 + 3116 + [[package]] 2074 3117 name = "rustix" 2075 - version = "1.0.8" 3118 + version = "0.38.44" 2076 3119 source = "registry+https://github.com/rust-lang/crates.io-index" 2077 - checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 3120 + checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 2078 3121 dependencies = [ 2079 3122 "bitflags", 2080 3123 "errno", ··· 2089 3132 source = "registry+https://github.com/rust-lang/crates.io-index" 2090 3133 checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" 2091 3134 dependencies = [ 3135 + "aws-lc-rs", 3136 + "log", 2092 3137 "once_cell", 2093 3138 "ring", 2094 3139 "rustls-pki-types", ··· 2103 3148 source = "registry+https://github.com/rust-lang/crates.io-index" 2104 3149 checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2105 3150 dependencies = [ 3151 + "web-time", 2106 3152 "zeroize", 2107 3153 ] 2108 3154 ··· 2112 3158 source = "registry+https://github.com/rust-lang/crates.io-index" 2113 3159 checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" 2114 3160 dependencies = [ 3161 + "aws-lc-rs", 2115 3162 "ring", 2116 3163 "rustls-pki-types", 2117 - "untrusted", 3164 + "untrusted 0.9.0", 2118 3165 ] 2119 3166 2120 3167 [[package]] ··· 2148 3195 ] 2149 3196 2150 3197 [[package]] 2151 - name = "schannel" 2152 - version = "0.1.27" 3198 + name = "schemars" 3199 + version = "0.9.0" 3200 + source = "registry+https://github.com/rust-lang/crates.io-index" 3201 + checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" 3202 + dependencies = [ 3203 + "dyn-clone", 3204 + "ref-cast", 3205 + "serde", 3206 + "serde_json", 3207 + ] 3208 + 3209 + [[package]] 3210 + name = "schemars" 3211 + version = "1.1.0" 2153 3212 source = "registry+https://github.com/rust-lang/crates.io-index" 2154 - checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 3213 + checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" 2155 3214 dependencies = [ 2156 - "windows-sys 0.59.0", 3215 + "dyn-clone", 3216 + "ref-cast", 3217 + "serde", 3218 + "serde_json", 2157 3219 ] 2158 3220 2159 3221 [[package]] ··· 2175 3237 ] 2176 3238 2177 3239 [[package]] 3240 + name = "sec1" 3241 + version = "0.7.3" 3242 + source = "registry+https://github.com/rust-lang/crates.io-index" 3243 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 3244 + dependencies = [ 3245 + "base16ct", 3246 + "der", 3247 + "generic-array", 3248 + "pkcs8", 3249 + "subtle", 3250 + "zeroize", 3251 + ] 3252 + 3253 + [[package]] 2178 3254 name = "secp256k1" 2179 3255 version = "0.28.2" 2180 3256 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2193 3269 ] 2194 3270 2195 3271 [[package]] 2196 - name = "security-framework" 2197 - version = "2.11.1" 3272 + name = "serde" 3273 + version = "1.0.228" 2198 3274 source = "registry+https://github.com/rust-lang/crates.io-index" 2199 - checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 3275 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 2200 3276 dependencies = [ 2201 - "bitflags", 2202 - "core-foundation", 2203 - "core-foundation-sys", 2204 - "libc", 2205 - "security-framework-sys", 3277 + "serde_core", 3278 + "serde_derive", 2206 3279 ] 2207 3280 2208 3281 [[package]] 2209 - name = "security-framework-sys" 2210 - version = "2.14.0" 3282 + name = "serde_bytes" 3283 + version = "0.11.19" 2211 3284 source = "registry+https://github.com/rust-lang/crates.io-index" 2212 - checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 3285 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 2213 3286 dependencies = [ 2214 - "core-foundation-sys", 2215 - "libc", 3287 + "serde", 3288 + "serde_core", 2216 3289 ] 2217 3290 2218 3291 [[package]] 2219 - name = "serde" 2220 - version = "1.0.219" 3292 + name = "serde_core" 3293 + version = "1.0.228" 2221 3294 source = "registry+https://github.com/rust-lang/crates.io-index" 2222 - checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 3295 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 2223 3296 dependencies = [ 2224 3297 "serde_derive", 2225 3298 ] 2226 3299 2227 3300 [[package]] 2228 3301 name = "serde_derive" 2229 - version = "1.0.219" 3302 + version = "1.0.228" 2230 3303 source = "registry+https://github.com/rust-lang/crates.io-index" 2231 - checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 3304 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 2232 3305 dependencies = [ 2233 3306 "proc-macro2", 2234 3307 "quote", 2235 - "syn", 3308 + "syn 2.0.105", 3309 + ] 3310 + 3311 + [[package]] 3312 + name = "serde_html_form" 3313 + version = "0.2.8" 3314 + source = "registry+https://github.com/rust-lang/crates.io-index" 3315 + checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" 3316 + dependencies = [ 3317 + "form_urlencoded", 3318 + "indexmap 2.10.0", 3319 + "itoa", 3320 + "ryu", 3321 + "serde_core", 3322 + ] 3323 + 3324 + [[package]] 3325 + name = "serde_ipld_dagcbor" 3326 + version = "0.6.4" 3327 + source = "registry+https://github.com/rust-lang/crates.io-index" 3328 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 3329 + dependencies = [ 3330 + "cbor4ii", 3331 + "ipld-core", 3332 + "scopeguard", 3333 + "serde", 2236 3334 ] 2237 3335 2238 3336 [[package]] 2239 3337 name = "serde_json" 2240 - version = "1.0.142" 3338 + version = "1.0.145" 2241 3339 source = "registry+https://github.com/rust-lang/crates.io-index" 2242 - checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 3340 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 2243 3341 dependencies = [ 3342 + "indexmap 2.10.0", 2244 3343 "itoa", 2245 3344 "memchr", 2246 3345 "ryu", 2247 3346 "serde", 3347 + "serde_core", 2248 3348 ] 2249 3349 2250 3350 [[package]] ··· 2258 3358 ] 2259 3359 2260 3360 [[package]] 3361 + name = "serde_repr" 3362 + version = "0.1.20" 3363 + source = "registry+https://github.com/rust-lang/crates.io-index" 3364 + checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 3365 + dependencies = [ 3366 + "proc-macro2", 3367 + "quote", 3368 + "syn 2.0.105", 3369 + ] 3370 + 3371 + [[package]] 2261 3372 name = "serde_urlencoded" 2262 3373 version = "0.7.1" 2263 3374 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2270 3381 ] 2271 3382 2272 3383 [[package]] 3384 + name = "serde_with" 3385 + version = "3.16.0" 3386 + source = "registry+https://github.com/rust-lang/crates.io-index" 3387 + checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" 3388 + dependencies = [ 3389 + "base64", 3390 + "chrono", 3391 + "hex", 3392 + "indexmap 1.9.3", 3393 + "indexmap 2.10.0", 3394 + "schemars 0.9.0", 3395 + "schemars 1.1.0", 3396 + "serde_core", 3397 + "serde_json", 3398 + "serde_with_macros", 3399 + "time", 3400 + ] 3401 + 3402 + [[package]] 3403 + name = "serde_with_macros" 3404 + version = "3.16.0" 3405 + source = "registry+https://github.com/rust-lang/crates.io-index" 3406 + checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" 3407 + dependencies = [ 3408 + "darling 0.21.3", 3409 + "proc-macro2", 3410 + "quote", 3411 + "syn 2.0.105", 3412 + ] 3413 + 3414 + [[package]] 2273 3415 name = "sha1" 2274 3416 version = "0.10.6" 2275 3417 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2326 3468 ] 2327 3469 2328 3470 [[package]] 3471 + name = "simd-adler32" 3472 + version = "0.3.7" 3473 + source = "registry+https://github.com/rust-lang/crates.io-index" 3474 + checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 3475 + 3476 + [[package]] 2329 3477 name = "slab" 2330 3478 version = "0.4.11" 2331 3479 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2341 3489 ] 2342 3490 2343 3491 [[package]] 3492 + name = "smol_str" 3493 + version = "0.3.4" 3494 + source = "registry+https://github.com/rust-lang/crates.io-index" 3495 + checksum = "3498b0a27f93ef1402f20eefacfaa1691272ac4eca1cdc8c596cb0a245d6cbf5" 3496 + dependencies = [ 3497 + "borsh", 3498 + "serde_core", 3499 + ] 3500 + 3501 + [[package]] 2344 3502 name = "socket2" 2345 3503 version = "0.6.0" 2346 3504 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2410 3568 "futures-util", 2411 3569 "hashbrown 0.15.5", 2412 3570 "hashlink", 2413 - "indexmap", 3571 + "indexmap 2.10.0", 2414 3572 "log", 2415 3573 "memchr", 2416 3574 "once_cell", ··· 2438 3596 "quote", 2439 3597 "sqlx-core", 2440 3598 "sqlx-macros-core", 2441 - "syn", 3599 + "syn 2.0.105", 2442 3600 ] 2443 3601 2444 3602 [[package]] ··· 2449 3607 dependencies = [ 2450 3608 "dotenvy", 2451 3609 "either", 2452 - "heck", 3610 + "heck 0.5.0", 2453 3611 "hex", 2454 3612 "once_cell", 2455 3613 "proc-macro2", ··· 2461 3619 "sqlx-mysql", 2462 3620 "sqlx-postgres", 2463 3621 "sqlx-sqlite", 2464 - "syn", 3622 + "syn 2.0.105", 2465 3623 "tokio", 2466 3624 "url", 2467 3625 ] ··· 2592 3750 ] 2593 3751 2594 3752 [[package]] 3753 + name = "static-regular-grammar" 3754 + version = "2.0.2" 3755 + source = "registry+https://github.com/rust-lang/crates.io-index" 3756 + checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" 3757 + dependencies = [ 3758 + "abnf", 3759 + "btree-range-map", 3760 + "ciborium", 3761 + "hex_fmt", 3762 + "indoc", 3763 + "proc-macro-error", 3764 + "proc-macro2", 3765 + "quote", 3766 + "serde", 3767 + "sha2", 3768 + "syn 2.0.105", 3769 + "thiserror 1.0.69", 3770 + ] 3771 + 3772 + [[package]] 3773 + name = "static_assertions" 3774 + version = "1.1.0" 3775 + source = "registry+https://github.com/rust-lang/crates.io-index" 3776 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 3777 + 3778 + [[package]] 2595 3779 name = "stringprep" 2596 3780 version = "0.1.5" 2597 3781 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2616 3800 2617 3801 [[package]] 2618 3802 name = "syn" 3803 + version = "1.0.109" 3804 + source = "registry+https://github.com/rust-lang/crates.io-index" 3805 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 3806 + dependencies = [ 3807 + "proc-macro2", 3808 + "quote", 3809 + "unicode-ident", 3810 + ] 3811 + 3812 + [[package]] 3813 + name = "syn" 2619 3814 version = "2.0.105" 2620 3815 source = "registry+https://github.com/rust-lang/crates.io-index" 2621 3816 checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" ··· 2630 3825 version = "1.0.2" 2631 3826 source = "registry+https://github.com/rust-lang/crates.io-index" 2632 3827 checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3828 + dependencies = [ 3829 + "futures-core", 3830 + ] 2633 3831 2634 3832 [[package]] 2635 3833 name = "synstructure" ··· 2639 3837 dependencies = [ 2640 3838 "proc-macro2", 2641 3839 "quote", 2642 - "syn", 3840 + "syn 2.0.105", 3841 + ] 3842 + 3843 + [[package]] 3844 + name = "system-configuration" 3845 + version = "0.6.1" 3846 + source = "registry+https://github.com/rust-lang/crates.io-index" 3847 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 3848 + dependencies = [ 3849 + "bitflags", 3850 + "core-foundation", 3851 + "system-configuration-sys", 2643 3852 ] 2644 3853 2645 3854 [[package]] 2646 - name = "tempfile" 2647 - version = "3.21.0" 3855 + name = "system-configuration-sys" 3856 + version = "0.6.0" 2648 3857 source = "registry+https://github.com/rust-lang/crates.io-index" 2649 - checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" 3858 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2650 3859 dependencies = [ 2651 - "fastrand", 2652 - "getrandom 0.3.3", 2653 - "once_cell", 2654 - "rustix", 2655 - "windows-sys 0.59.0", 3860 + "core-foundation-sys", 3861 + "libc", 2656 3862 ] 2657 3863 2658 3864 [[package]] ··· 2681 3887 dependencies = [ 2682 3888 "proc-macro2", 2683 3889 "quote", 2684 - "syn", 3890 + "syn 2.0.105", 2685 3891 ] 2686 3892 2687 3893 [[package]] ··· 2692 3898 dependencies = [ 2693 3899 "proc-macro2", 2694 3900 "quote", 2695 - "syn", 3901 + "syn 2.0.105", 2696 3902 ] 2697 3903 2698 3904 [[package]] ··· 2705 3911 ] 2706 3912 2707 3913 [[package]] 3914 + name = "time" 3915 + version = "0.3.44" 3916 + source = "registry+https://github.com/rust-lang/crates.io-index" 3917 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 3918 + dependencies = [ 3919 + "deranged", 3920 + "itoa", 3921 + "num-conv", 3922 + "powerfmt", 3923 + "serde", 3924 + "time-core", 3925 + "time-macros", 3926 + ] 3927 + 3928 + [[package]] 3929 + name = "time-core" 3930 + version = "0.1.6" 3931 + source = "registry+https://github.com/rust-lang/crates.io-index" 3932 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 3933 + 3934 + [[package]] 3935 + name = "time-macros" 3936 + version = "0.2.24" 3937 + source = "registry+https://github.com/rust-lang/crates.io-index" 3938 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 3939 + dependencies = [ 3940 + "num-conv", 3941 + "time-core", 3942 + ] 3943 + 3944 + [[package]] 2708 3945 name = "tinystr" 2709 3946 version = "0.8.1" 2710 3947 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2756 3993 dependencies = [ 2757 3994 "proc-macro2", 2758 3995 "quote", 2759 - "syn", 3996 + "syn 2.0.105", 2760 3997 ] 2761 3998 2762 3999 [[package]] 2763 - name = "tokio-native-tls" 2764 - version = "0.3.1" 4000 + name = "tokio-rustls" 4001 + version = "0.26.2" 2765 4002 source = "registry+https://github.com/rust-lang/crates.io-index" 2766 - checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 4003 + checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 2767 4004 dependencies = [ 2768 - "native-tls", 4005 + "rustls", 2769 4006 "tokio", 2770 4007 ] 2771 4008 ··· 2782 4019 2783 4020 [[package]] 2784 4021 name = "tokio-util" 2785 - version = "0.7.15" 4022 + version = "0.7.17" 2786 4023 source = "registry+https://github.com/rust-lang/crates.io-index" 2787 - checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 4024 + checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 2788 4025 dependencies = [ 2789 4026 "bytes", 2790 4027 "futures-core", ··· 2830 4067 dependencies = [ 2831 4068 "futures-core", 2832 4069 "futures-util", 2833 - "indexmap", 4070 + "indexmap 2.10.0", 2834 4071 "pin-project-lite", 2835 4072 "slab", 2836 4073 "sync_wrapper", ··· 2851 4088 "bitflags", 2852 4089 "bytes", 2853 4090 "futures-core", 4091 + "futures-util", 2854 4092 "http", 2855 4093 "http-body", 4094 + "iri-string", 2856 4095 "pin-project-lite", 2857 4096 "tokio", 2858 4097 "tokio-util", 4098 + "tower", 2859 4099 "tower-layer", 2860 4100 "tower-service", 2861 4101 ] ··· 2909 4149 dependencies = [ 2910 4150 "proc-macro2", 2911 4151 "quote", 2912 - "syn", 4152 + "syn 2.0.105", 2913 4153 ] 2914 4154 2915 4155 [[package]] ··· 2952 4192 ] 2953 4193 2954 4194 [[package]] 4195 + name = "trait-variant" 4196 + version = "0.1.2" 4197 + source = "registry+https://github.com/rust-lang/crates.io-index" 4198 + checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 4199 + dependencies = [ 4200 + "proc-macro2", 4201 + "quote", 4202 + "syn 2.0.105", 4203 + ] 4204 + 4205 + [[package]] 2955 4206 name = "try-lock" 2956 4207 version = "0.2.5" 2957 4208 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2997 4248 checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 2998 4249 2999 4250 [[package]] 4251 + name = "unicode-segmentation" 4252 + version = "1.12.0" 4253 + source = "registry+https://github.com/rust-lang/crates.io-index" 4254 + checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 4255 + 4256 + [[package]] 4257 + name = "unicode-width" 4258 + version = "0.1.14" 4259 + source = "registry+https://github.com/rust-lang/crates.io-index" 4260 + checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 4261 + 4262 + [[package]] 4263 + name = "unsigned-varint" 4264 + version = "0.8.0" 4265 + source = "registry+https://github.com/rust-lang/crates.io-index" 4266 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 4267 + 4268 + [[package]] 4269 + name = "untrusted" 4270 + version = "0.7.1" 4271 + source = "registry+https://github.com/rust-lang/crates.io-index" 4272 + checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 4273 + 4274 + [[package]] 3000 4275 name = "untrusted" 3001 4276 version = "0.9.0" 3002 4277 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3011 4286 "form_urlencoded", 3012 4287 "idna", 3013 4288 "percent-encoding", 4289 + "serde", 3014 4290 ] 3015 4291 3016 4292 [[package]] 4293 + name = "urlencoding" 4294 + version = "2.1.3" 4295 + source = "registry+https://github.com/rust-lang/crates.io-index" 4296 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 4297 + 4298 + [[package]] 4299 + name = "utf8-width" 4300 + version = "0.1.8" 4301 + source = "registry+https://github.com/rust-lang/crates.io-index" 4302 + checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" 4303 + 4304 + [[package]] 3017 4305 name = "utf8_iter" 3018 4306 version = "1.0.4" 3019 4307 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3063 4351 checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3064 4352 3065 4353 [[package]] 3066 - name = "wasi" 3067 - version = "0.14.2+wasi-0.2.4" 4354 + name = "wasip2" 4355 + version = "1.0.1+wasi-0.2.4" 3068 4356 source = "registry+https://github.com/rust-lang/crates.io-index" 3069 - checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 4357 + checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 3070 4358 dependencies = [ 3071 - "wit-bindgen-rt", 4359 + "wit-bindgen", 3072 4360 ] 3073 4361 3074 4362 [[package]] ··· 3099 4387 "log", 3100 4388 "proc-macro2", 3101 4389 "quote", 3102 - "syn", 4390 + "syn 2.0.105", 3103 4391 "wasm-bindgen-shared", 3104 4392 ] 3105 4393 3106 4394 [[package]] 4395 + name = "wasm-bindgen-futures" 4396 + version = "0.4.50" 4397 + source = "registry+https://github.com/rust-lang/crates.io-index" 4398 + checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 4399 + dependencies = [ 4400 + "cfg-if", 4401 + "js-sys", 4402 + "once_cell", 4403 + "wasm-bindgen", 4404 + "web-sys", 4405 + ] 4406 + 4407 + [[package]] 3107 4408 name = "wasm-bindgen-macro" 3108 4409 version = "0.2.100" 3109 4410 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3121 4422 dependencies = [ 3122 4423 "proc-macro2", 3123 4424 "quote", 3124 - "syn", 4425 + "syn 2.0.105", 3125 4426 "wasm-bindgen-backend", 3126 4427 "wasm-bindgen-shared", 3127 4428 ] ··· 3136 4437 ] 3137 4438 3138 4439 [[package]] 4440 + name = "wasm-streams" 4441 + version = "0.4.2" 4442 + source = "registry+https://github.com/rust-lang/crates.io-index" 4443 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4444 + dependencies = [ 4445 + "futures-util", 4446 + "js-sys", 4447 + "wasm-bindgen", 4448 + "wasm-bindgen-futures", 4449 + "web-sys", 4450 + ] 4451 + 4452 + [[package]] 3139 4453 name = "web-sys" 3140 4454 version = "0.3.77" 3141 4455 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3174 4488 ] 3175 4489 3176 4490 [[package]] 4491 + name = "which" 4492 + version = "4.4.2" 4493 + source = "registry+https://github.com/rust-lang/crates.io-index" 4494 + checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 4495 + dependencies = [ 4496 + "either", 4497 + "home", 4498 + "once_cell", 4499 + "rustix", 4500 + ] 4501 + 4502 + [[package]] 3177 4503 name = "whoami" 3178 4504 version = "1.6.1" 3179 4505 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3222 4548 dependencies = [ 3223 4549 "windows-implement", 3224 4550 "windows-interface", 3225 - "windows-link", 4551 + "windows-link 0.1.3", 3226 4552 "windows-result", 3227 4553 "windows-strings", 3228 4554 ] ··· 3235 4561 dependencies = [ 3236 4562 "proc-macro2", 3237 4563 "quote", 3238 - "syn", 4564 + "syn 2.0.105", 3239 4565 ] 3240 4566 3241 4567 [[package]] ··· 3246 4572 dependencies = [ 3247 4573 "proc-macro2", 3248 4574 "quote", 3249 - "syn", 4575 + "syn 2.0.105", 3250 4576 ] 3251 4577 3252 4578 [[package]] ··· 3256 4582 checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3257 4583 3258 4584 [[package]] 4585 + name = "windows-link" 4586 + version = "0.2.1" 4587 + source = "registry+https://github.com/rust-lang/crates.io-index" 4588 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4589 + 4590 + [[package]] 4591 + name = "windows-registry" 4592 + version = "0.5.3" 4593 + source = "registry+https://github.com/rust-lang/crates.io-index" 4594 + checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" 4595 + dependencies = [ 4596 + "windows-link 0.1.3", 4597 + "windows-result", 4598 + "windows-strings", 4599 + ] 4600 + 4601 + [[package]] 3259 4602 name = "windows-result" 3260 4603 version = "0.3.4" 3261 4604 source = "registry+https://github.com/rust-lang/crates.io-index" 3262 4605 checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3263 4606 dependencies = [ 3264 - "windows-link", 4607 + "windows-link 0.1.3", 3265 4608 ] 3266 4609 3267 4610 [[package]] ··· 3270 4613 source = "registry+https://github.com/rust-lang/crates.io-index" 3271 4614 checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3272 4615 dependencies = [ 3273 - "windows-link", 4616 + "windows-link 0.1.3", 3274 4617 ] 3275 4618 3276 4619 [[package]] ··· 3422 4765 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3423 4766 3424 4767 [[package]] 3425 - name = "wit-bindgen-rt" 3426 - version = "0.39.0" 4768 + name = "wit-bindgen" 4769 + version = "0.46.0" 3427 4770 source = "registry+https://github.com/rust-lang/crates.io-index" 3428 - checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 3429 - dependencies = [ 3430 - "bitflags", 3431 - ] 4771 + checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 3432 4772 3433 4773 [[package]] 3434 4774 name = "writeable" ··· 3437 4777 checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 3438 4778 3439 4779 [[package]] 4780 + name = "yansi" 4781 + version = "1.0.1" 4782 + source = "registry+https://github.com/rust-lang/crates.io-index" 4783 + checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 4784 + 4785 + [[package]] 3440 4786 name = "yoke" 3441 4787 version = "0.8.0" 3442 4788 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3456 4802 dependencies = [ 3457 4803 "proc-macro2", 3458 4804 "quote", 3459 - "syn", 4805 + "syn 2.0.105", 3460 4806 "synstructure", 3461 4807 ] 3462 4808 ··· 3477 4823 dependencies = [ 3478 4824 "proc-macro2", 3479 4825 "quote", 3480 - "syn", 4826 + "syn 2.0.105", 3481 4827 ] 3482 4828 3483 4829 [[package]] ··· 3497 4843 dependencies = [ 3498 4844 "proc-macro2", 3499 4845 "quote", 3500 - "syn", 4846 + "syn 2.0.105", 3501 4847 "synstructure", 3502 4848 ] 3503 4849 ··· 3518 4864 dependencies = [ 3519 4865 "proc-macro2", 3520 4866 "quote", 3521 - "syn", 4867 + "syn 2.0.105", 3522 4868 ] 3523 4869 3524 4870 [[package]] ··· 3551 4897 dependencies = [ 3552 4898 "proc-macro2", 3553 4899 "quote", 3554 - "syn", 4900 + "syn 2.0.105", 3555 4901 ] 3556 4902 3557 4903 [[package]]
+15 -4
Cargo.toml
··· 1 1 [package] 2 2 name = "pds_gatekeeper" 3 - version = "0.1.0" 3 + version = "0.1.2" 4 4 edition = "2024" 5 + license = "MIT" 5 6 6 7 [dependencies] 7 8 axum = { version = "0.8.4", features = ["macros", "json"] } ··· 14 15 tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } 15 16 hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] } 16 17 tower-http = { version = "0.6", features = ["cors", "compression-zstd"] } 17 - tower_governor = "0.8.0" 18 + tower_governor = { version = "0.8.0", features = ["axum", "tracing"] } 18 19 hex = "0.4" 19 20 jwt-compact = { version = "0.8.0", features = ["es256k"] } 20 21 scrypt = "0.11" 21 - lettre = { version = "0.11.18", features = ["tokio1", "pool", "tokio1-native-tls"] } 22 + #Leaveing these two cause I think it is needed by the email crate for ssl 23 + aws-lc-rs = "1.13.0" 24 + rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] } 25 + lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] } 22 26 handlebars = { version = "6.3.2", features = ["rust-embed"] } 23 27 rust-embed = "8.7.2" 24 28 axum-template = { version = "3.0.0", features = ["handlebars"] } 25 29 rand = "0.9.2" 26 30 anyhow = "1.0.99" 27 - chrono = "0.4.41" 31 + chrono = { version = "0.4.42", features = ["default", "serde"] } 28 32 sha2 = "0.10" 33 + jacquard-common = "0.9.2" 34 + jacquard-identity = "0.9.2" 35 + multibase = "0.9.2" 36 + reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } 37 + urlencoding = "2.1" 38 + html-escape = "0.2.13" 39 + josekit = "0.10.3"
+10
Dockerfile
··· 1 + FROM rust:1.89.0-bookworm AS builder 2 + WORKDIR /app 3 + COPY ../ /app 4 + RUN cargo build --release 5 + # 6 + FROM rust:1.89-slim-bookworm AS api 7 + RUN apt-get update 8 + RUN apt-get install -y ca-certificates 9 + COPY --from=builder /app/target/release/pds_gatekeeper /usr/local/bin/pds_gatekeeper 10 + CMD ["pds_gatekeeper"]
+21
LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Bailey Townsend 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+171 -12
README.md
··· 15 15 - Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins 16 16 - Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA 17 17 18 - ## Captcha on Create Account 18 + ## Captcha on account creation 19 19 20 - Future feature? 20 + Require a `verificationCode` set on the `createAccount` request. This is gotten from completing a captcha challenge 21 + hosted on the 22 + PDS mimicking what the Bluesky Entryway does. Migration tools will need to support this, but social-apps will support 23 + and redirect to `GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT`. This is how the clients know to get the code to prove a captcha 24 + was successful. 25 + 26 + - Requires `GATEKEEPER_CREATE_ACCOUNT_CAPTCHA` to be set to true. 27 + - Requires `PDS_HCAPTCHA_SITE_KEY` and `PDS_HCAPTCHA_SECRET_KEY` to be set. Can sign up at https://www.hcaptcha.com/ 28 + - Requires proxying `/xrpc/com.atproto.server.describeServer`, `/xrpc/com.atproto.server.createAccount` and `/gate/*` to 29 + PDS 30 + Gatekeeper 31 + - Optional `GATEKEEPER_JWE_KEY` key to encrypt the captcha verification code. Defaults to a random 32 byte key. Not 32 + strictly needed unless you're scaling 33 + - Optional`GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT` default redirect on captcha success. Defaults to `https://bsky.app`. 34 + - Optional `GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS` allowed redirect urls for captcha success. You want these to match the 35 + url showing the captcha. Defaults are: 36 + - https://bsky.app 37 + - https://pdsmoover.com 38 + - https://blacksky.community 39 + - https://tektite.cc 40 + 41 + ## Block account creation unless it's a migration 42 + 43 + You can set `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` to block createAccount unless it's via a migration. This does not require 44 + a change for migration tools, but social-apps create a new account will no longer work and to create a brand new account 45 + users will need to do this via the Oauth account create screen on the PDS. We recommend setting `PDS_HCAPTCHA_SITE_KEY` 46 + and `PDS_HCAPTCHA_SECRET_KEY` so the OAuth screen is protected by a captcha if you use this with invite codes turned 47 + off. 21 48 22 49 # Setup 23 50 24 - We are getting close! Testing now 51 + PDS Gatekeeper has 2 parts to its setup, docker compose file and a reverse proxy (Caddy in this case). I will be 52 + assuming you setup the PDS following the directions 53 + found [here](https://atproto.com/guides/self-hosting), but if yours is different, or you have questions, feel free to 54 + let 55 + me know, and we can figure it out. 56 + 57 + ## Docker compose 58 + 59 + The pds gatekeeper container can be found on docker hub under the name `fatfingers23/pds_gatekeeper`. The container does 60 + need access to the `/pds` root folder to access the same db's as your PDS. The part you need to add would look a bit 61 + like below. You can find a full example of what I use for my pds at [./examples/compose.yml](./examples/compose.yml). 62 + This is usually found at `/pds/compose.yaml`on your PDS> 63 + 64 + ```yml 65 + gatekeeper: 66 + container_name: gatekeeper 67 + image: fatfingers23/pds_gatekeeper:latest 68 + network_mode: host 69 + restart: unless-stopped 70 + #This gives the container to the access to the PDS folder. Source is the location on your server of that directory 71 + volumes: 72 + - type: bind 73 + source: /pds 74 + target: /pds 75 + depends_on: 76 + - pds 77 + ``` 78 + 79 + For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up 80 + correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml). 81 + 82 + ```yml 83 + gatekeeper: 84 + container_name: gatekeeper 85 + image: 'fatfingers23/pds_gatekeeper:latest' 86 + restart: unless-stopped 87 + volumes: 88 + - '/pds:/pds' 89 + environment: 90 + - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 91 + - 'PDS_BASE_URL=http://pds:3000' 92 + - GATEKEEPER_HOST=0.0.0.0 93 + depends_on: 94 + - pds 95 + healthcheck: 96 + test: 97 + - CMD 98 + - timeout 99 + - '1' 100 + - bash 101 + - '-c' 102 + - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080' 103 + interval: 10s 104 + timeout: 5s 105 + retries: 3 106 + start_period: 10s 107 + labels: 108 + - traefik.enable=true 109 + - 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))' 110 + - traefik.http.routers.pds-gatekeeper.entrypoints=https 111 + - traefik.http.routers.pds-gatekeeper.tls=true 112 + - traefik.http.routers.pds-gatekeeper.priority=100 113 + - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors 114 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080 115 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http 116 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH' 117 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*' 118 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*' 119 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100 120 + - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true 121 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true 122 + ``` 25 123 26 - Nothing here yet! If you are brave enough to try before full release, let me know and I'll help you set it up. 27 - But I want to run it locally on my own PDS first to test run it a bit. 124 + ## Caddy setup 28 125 29 - Example Caddyfile (mostly so I don't lose it for now. Will have a better one in the future) 126 + For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add 127 + in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile). 128 + This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS. 30 129 31 - ```caddyfile 32 - http://localhost { 130 + ``` 131 + @gatekeeper { 132 + path /xrpc/com.atproto.server.getSession 133 + path /xrpc/com.atproto.server.describeServer 134 + path /xrpc/com.atproto.server.updateEmail 135 + path /xrpc/com.atproto.server.createSession 136 + path /xrpc/com.atproto.server.createAccount 137 + path /@atproto/oauth-provider/~api/sign-in 138 + path /gate/* 139 + } 140 + 141 + handle @gatekeeper { 142 + reverse_proxy http://localhost:8080 143 + } 33 144 145 + reverse_proxy http://localhost:3000 146 + ``` 147 + 148 + If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to 149 + `localhost:8081` (or w/e port you want). 150 + 151 + ``` 152 + http://*.localhost:8082, http://localhost:8082 { 34 153 @gatekeeper { 35 154 path /xrpc/com.atproto.server.getSession 155 + path /xrpc/com.atproto.server.describeServer 36 156 path /xrpc/com.atproto.server.updateEmail 37 157 path /xrpc/com.atproto.server.createSession 158 + path /xrpc/com.atproto.server.createAccount 38 159 path /@atproto/oauth-provider/~api/sign-in 160 + path /gate/* 39 161 } 40 162 41 163 handle @gatekeeper { 42 - reverse_proxy http://localhost:8080 164 + #This is the address for PDS gatekeeper, default is 8080 165 + reverse_proxy http://localhost:8080 166 + #Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper 167 + header_up X-Forwarded-For {http.request.header.CF-Connecting-IP} 43 168 } 169 + reverse_proxy http://localhost:3000 170 + } 171 + 172 + ``` 173 + 174 + # Environment variables and bonuses 175 + 176 + Every environment variable can be set in the `pds.env` and shared between PDS and gatekeeper and the PDS, with the 177 + exception of `PDS_ENV_LOCATION`. This can be set to load the pds.env, by default it checks `/pds/pds.env` and is 178 + recommended to mount the `/pds` folder on the server to `/pds` in the pds gatekeeper container. 179 + 180 + `PDS_DATA_DIRECTORY` - Root directory of the PDS. Same as the one found in `pds.env` this is how pds gatekeeper knows 181 + knows the rest of the environment variables. 182 + 183 + `GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY` - The folder for templates of the emails PDS gatekeeper sends. You can find them 184 + in [./email_templates](./email_templates). You are free to edit them as you please and set this variable to a location 185 + in the pds gateekeper container and it will use them in place of the default ones. Just make sure ot keep the names the 186 + same. 44 187 45 - reverse_proxy /* http://localhost:3000 46 - } 188 + `GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT` - Subject of the email sent to the user when they turn on 2FA. Defaults to 189 + `Sign in to Bluesky` 190 + 191 + `PDS_BASE_URL` - Base url of the PDS. You most likely want `https://localhost:3000` which is also the default 192 + 193 + `GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1` 194 + 195 + `GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080` 47 196 48 - ``` 197 + `GATEKEEPER_CREATE_ACCOUNT_PER_SECOND` - Sets how often it takes a count off the limiter. example if you hit the rate 198 + limit of 5 and set to 60, then in 60 seconds you will be able to make one more. Or in 5 minutes be able to make 5 more. 199 + 200 + `GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where 201 + the 5 comes from. Example can set this to 10 to allow for 10 requests in a burst, and after 60 seconds it will drop one 202 + off. 203 + 204 + `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` - Defaults false. If set to true, will only allow the 205 + `/xrpc/com.atproto.server.createAccount` endpoint to be used for migrations. Meaning it will check for the serviceAuth 206 + token and verify it is valid. 207 +
+30
examples/Caddyfile
··· 1 + { 2 + email youremail@myemail.com 3 + on_demand_tls { 4 + ask http://localhost:3000/tls-check 5 + } 6 + } 7 + 8 + *.yourpds.com, yourpds.com { 9 + tls { 10 + on_demand 11 + } 12 + # You'll most likely just want from here to.... 13 + @gatekeeper { 14 + path /xrpc/com.atproto.server.getSession 15 + path /xrpc/com.atproto.server.describeServer 16 + path /xrpc/com.atproto.server.updateEmail 17 + path /xrpc/com.atproto.server.createSession 18 + path /xrpc/com.atproto.server.createAccount 19 + path /@atproto/oauth-provider/~api/sign-in 20 + path /gate/* 21 + } 22 + 23 + handle @gatekeeper { 24 + #This is the address for PDS gatekeeper, default is 8080 25 + reverse_proxy http://localhost:8080 26 + } 27 + 28 + reverse_proxy http://localhost:3000 29 + #..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line 30 + }
+51
examples/compose.yml
··· 1 + version: '3.9' 2 + services: 3 + caddy: 4 + container_name: caddy 5 + image: caddy:2 6 + network_mode: host 7 + depends_on: 8 + - pds 9 + restart: unless-stopped 10 + volumes: 11 + - type: bind 12 + source: /pds/caddy/data 13 + target: /data 14 + - type: bind 15 + source: /pds/caddy/etc/caddy 16 + target: /etc/caddy 17 + pds: 18 + container_name: pds 19 + image: ghcr.io/bluesky-social/pds:0.4 20 + network_mode: host 21 + restart: unless-stopped 22 + volumes: 23 + - type: bind 24 + source: /pds 25 + target: /pds 26 + env_file: 27 + - /pds/pds.env 28 + watchtower: 29 + container_name: watchtower 30 + image: containrrr/watchtower:latest 31 + network_mode: host 32 + volumes: 33 + - type: bind 34 + source: /var/run/docker.sock 35 + target: /var/run/docker.sock 36 + restart: unless-stopped 37 + environment: 38 + WATCHTOWER_CLEANUP: true 39 + WATCHTOWER_SCHEDULE: "@midnight" 40 + gatekeeper: 41 + container_name: gatekeeper 42 + image: fatfingers23/pds_gatekeeper:latest 43 + network_mode: host 44 + restart: unless-stopped 45 + #This gives the container to the access to the PDS folder. Source is the location on your server of that directory 46 + volumes: 47 + - type: bind 48 + source: /pds 49 + target: /pds 50 + depends_on: 51 + - pds
+73
examples/coolify-compose.yml
··· 1 + services: 2 + pds: 3 + image: 'ghcr.io/bluesky-social/pds:0.4.182' 4 + volumes: 5 + - '/pds:/pds' 6 + environment: 7 + - SERVICE_URL_PDS_3000 8 + - 'PDS_HOSTNAME=${SERVICE_FQDN_PDS_3000}' 9 + - 'PDS_JWT_SECRET=${SERVICE_HEX_32_JWTSECRET}' 10 + - 'PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}' 11 + - 'PDS_ADMIN_EMAIL=${PDS_ADMIN_EMAIL}' 12 + - 'PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${SERVICE_HEX_32_ROTATIONKEY}' 13 + - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 14 + - 'PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATA_DIRECTORY:-/pds}/blocks' 15 + - 'PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-104857600}' 16 + - 'PDS_DID_PLC_URL=${PDS_DID_PLC_URL:-https://plc.directory}' 17 + - 'PDS_EMAIL_FROM_ADDRESS=${PDS_EMAIL_FROM_ADDRESS}' 18 + - 'PDS_EMAIL_SMTP_URL=${PDS_EMAIL_SMTP_URL}' 19 + - 'PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL:-https://api.bsky.app}' 20 + - 'PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID:-did:web:api.bsky.app}' 21 + - 'PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL:-https://mod.bsky.app/xrpc/com.atproto.moderation.createReport}' 22 + - 'PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID:-did:plc:ar7c4by46qjdydhdevvrndac}' 23 + - 'PDS_CRAWLERS=${PDS_CRAWLERS:-https://bsky.network}' 24 + - 'LOG_ENABLED=${LOG_ENABLED:-true}' 25 + command: "sh -c '\n set -euo pipefail\n echo \"Installing required packages and pdsadmin...\"\n apk add --no-cache openssl curl bash jq coreutils gnupg util-linux-misc >/dev/null\n curl -o /usr/local/bin/pdsadmin.sh https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin.sh\n chmod 700 /usr/local/bin/pdsadmin.sh\n ln -sf /usr/local/bin/pdsadmin.sh /usr/local/bin/pdsadmin\n echo \"Creating an empty pds.env file so pdsadmin works...\"\n touch ${PDS_DATA_DIRECTORY}/pds.env\n echo \"Launching PDS, enjoy!...\"\n exec node --enable-source-maps index.js\n'\n" 26 + healthcheck: 27 + test: 28 + - CMD 29 + - wget 30 + - '--spider' 31 + - 'http://127.0.0.1:3000/xrpc/_health' 32 + interval: 5s 33 + timeout: 10s 34 + retries: 10 35 + gatekeeper: 36 + container_name: gatekeeper 37 + image: 'fatfingers23/pds_gatekeeper:latest' 38 + restart: unless-stopped 39 + volumes: 40 + - '/pds:/pds' 41 + environment: 42 + - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 43 + - 'PDS_BASE_URL=http://pds:3000' 44 + - GATEKEEPER_HOST=0.0.0.0 45 + depends_on: 46 + - pds 47 + healthcheck: 48 + test: 49 + - CMD 50 + - timeout 51 + - '1' 52 + - bash 53 + - '-c' 54 + - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080' 55 + interval: 10s 56 + timeout: 5s 57 + retries: 3 58 + start_period: 10s 59 + labels: 60 + - traefik.enable=true 61 + - 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.describeServer`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`) || Path(`/gate`))' 62 + - traefik.http.routers.pds-gatekeeper.entrypoints=https 63 + - traefik.http.routers.pds-gatekeeper.tls=true 64 + - traefik.http.routers.pds-gatekeeper.priority=100 65 + - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors 66 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080 67 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http 68 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH' 69 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*' 70 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*' 71 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100 72 + - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true 73 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
+166
html_templates/captcha.hbs
··· 1 + <html lang="en" class=" "> 2 + <head> 3 + <meta charset="utf-8"/> 4 + <meta 5 + name="viewport" 6 + content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover" 7 + /> 8 + <meta name="referrer" content="origin-when-cross-origin"/> 9 + 10 + <title> 11 + {{pds}} - Captcha 12 + </title> 13 + <style> 14 + :root, 15 + :root.light-mode { 16 + --brand-color: rgb(16, 131, 254); 17 + --primary-color: rgb(7, 10, 13); 18 + --secondary-color: rgb(66, 86, 108); 19 + --bg-primary-color: rgb(255, 255, 255); 20 + --bg-secondary-color: rgb(240, 242, 245); 21 + } 22 + 23 + @media (prefers-color-scheme: dark) { 24 + :root { 25 + --brand-color: rgb(16, 131, 254); 26 + --primary-color: rgb(255, 255, 255); 27 + --secondary-color: rgb(133, 152, 173); 28 + --bg-primary-color: rgb(7, 10, 13); 29 + --bg-secondary-color: rgb(13, 18, 23); 30 + } 31 + } 32 + 33 + :root.dark-mode { 34 + --brand-color: rgb(16, 131, 254); 35 + --primary-color: rgb(255, 255, 255); 36 + --secondary-color: rgb(133, 152, 173); 37 + --bg-primary-color: rgb(7, 10, 13); 38 + --bg-secondary-color: rgb(13, 18, 23); 39 + } 40 + 41 + body { 42 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, 43 + Arial, sans-serif; 44 + background: var(--bg-primary-color); 45 + color: var(--primary-color); 46 + text-rendering: optimizeLegibility; 47 + -webkit-font-smoothing: antialiased; 48 + } 49 + 50 + .info { 51 + border-radius: 8px; 52 + padding: 12px 14px; 53 + letter-spacing: 0.25px; 54 + font-weight: 400; 55 + background-color: var(--bg-secondary-color); 56 + color: var(--secondary-color); 57 + } 58 + 59 + .gate-page, 60 + .error-page { 61 + margin: 0; 62 + margin-top: 10px; 63 + display: flex; 64 + justify-content: center; 65 + } 66 + 67 + .gate-page .main, 68 + .error-page .main { 69 + max-width: 600px; 70 + width: 100%; 71 + display: flex; 72 + flex-direction: column; 73 + } 74 + 75 + .gate-page .main { 76 + padding: 0; 77 + } 78 + 79 + .error-page .main { 80 + padding: 20px; 81 + } 82 + 83 + .gate-page .main > :not(:first-child), 84 + .error-page .main > :not(:first-child) { 85 + margin-top: 20px; 86 + } 87 + 88 + .gate-page #gate-form { 89 + margin: 0; 90 + } 91 + 92 + .gate-page #hcaptcha { 93 + display: flex; 94 + justify-content: center; 95 + } 96 + 97 + .pds-title { 98 + font-size: 2.25rem; 99 + font-weight: 700; 100 + line-height: 1.2; 101 + text-align: center; 102 + color: var(--primary-color); 103 + margin: 10px 0 16px; 104 + } 105 + 106 + </style> 107 + 108 + <script type="text/javascript"> 109 + function onCaptchaReady() { 110 + const theme = document.documentElement.classList.contains('dark-mode') ? 111 + 'dark' : 112 + document.documentElement.classList.contains('light-mode') ? 113 + 'light' : 114 + window.matchMedia('(prefers-color-scheme: dark)').matches ? 115 + 'dark' : 116 + 'light' 117 + hcaptcha.render('hcaptcha', {theme}); 118 + } 119 + 120 + function onCaptchaComplete() { 121 + setTimeout(function () { 122 + document.getElementById('gate-form').submit(); 123 + }, 1000); 124 + } 125 + 126 + function onCaptchaError() { 127 + const url = new URL(location.href); 128 + url.searchParams.set('error', 'true'); 129 + location.assign(url.search); 130 + } 131 + 132 + function onCaptchaExpired() { 133 + const url = new URL(location.href); 134 + url.searchParams.set('error', 'expired'); 135 + location.assign(url.search); 136 + } 137 + </script> 138 + <script 139 + src="https://js.hcaptcha.com/1/api.js?render=explicit&onload=onCaptchaReady" 140 + async 141 + defer 142 + ></script> 143 + <link rel="stylesheet" href="/gate/signup/assets/common.css"/> 144 + </head> 145 + 146 + <body class="gate-page"> 147 + <div class="main"> 148 + <div class="pds-title">{{pds}}</div> 149 + <form id="gate-form" action="" method="POST"> 150 + <div 151 + id="hcaptcha" 152 + data-sitekey="{{captcha_site_key}}" 153 + data-callback="onCaptchaComplete" 154 + data-error-callback="onCaptchaError" 155 + data-expired-callback="onCaptchaExpired" 156 + data-chalexpired-callback="onCaptchaExpired" 157 + ></div> 158 + <input type="hidden" name="redirect_url" value="{{redirect_url}}"/> 159 + </form> 160 + {{#if error_message }} 161 + <div class="info">{{error_message}}</div> 162 + {{/if}} 163 + 164 + </div> 165 + </body> 166 + </html>
+6
justfile
··· 1 + release: 2 + docker buildx build \ 3 + --platform linux/arm64,linux/amd64 \ 4 + --tag fatfingers23/pds_gatekeeper:latest \ 5 + --tag fatfingers23/pds_gatekeeper:0.1.0.5 \ 6 + --push .
+10
migrations/20251126000000_gate_codes.sql
··· 1 + -- Add migration script here 2 + CREATE TABLE IF NOT EXISTS gate_codes 3 + ( 4 + code VARCHAR PRIMARY KEY, 5 + handle VARCHAR NOT NULL, 6 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 7 + ); 8 + 9 + -- Index on created_at for efficient cleanup of expired codes 10 + CREATE INDEX IF NOT EXISTS idx_gate_codes_created_at ON gate_codes(created_at);
+247
src/gate.rs
··· 1 + use crate::AppState; 2 + use crate::helpers::{generate_gate_token, json_error_response}; 3 + use axum::Form; 4 + use axum::extract::{Query, State}; 5 + use axum::http::StatusCode; 6 + use axum::response::{IntoResponse, Redirect, Response}; 7 + use axum_template::RenderHtml; 8 + use chrono::{DateTime, Utc}; 9 + use serde::{Deserialize, Serialize}; 10 + use std::env; 11 + use tracing::log; 12 + 13 + #[derive(Deserialize)] 14 + pub struct GateQuery { 15 + handle: String, 16 + state: String, 17 + #[serde(default)] 18 + error: Option<String>, 19 + #[serde(default)] 20 + redirect_url: Option<String>, 21 + } 22 + 23 + #[derive(Deserialize, Serialize)] 24 + pub struct CaptchaPage { 25 + handle: String, 26 + state: String, 27 + captcha_site_key: String, 28 + error_message: Option<String>, 29 + pds: String, 30 + redirect_url: Option<String>, 31 + } 32 + 33 + #[derive(Deserialize)] 34 + pub struct CaptchaForm { 35 + #[serde(rename = "h-captcha-response")] 36 + h_captcha_response: String, 37 + #[serde(default)] 38 + redirect_url: Option<String>, 39 + } 40 + 41 + /// GET /gate - Display the captcha page 42 + pub async fn get_gate( 43 + Query(params): Query<GateQuery>, 44 + State(state): State<AppState>, 45 + ) -> impl IntoResponse { 46 + let hcaptcha_site_key = match env::var("PDS_HCAPTCHA_SITE_KEY") { 47 + Ok(key) => key, 48 + Err(_) => { 49 + return json_error_response( 50 + StatusCode::INTERNAL_SERVER_ERROR, 51 + "ServerError", 52 + "hCaptcha is not configured", 53 + ) 54 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 55 + } 56 + }; 57 + 58 + let error_message = match params.error { 59 + None => None, 60 + Some(error) => Some(html_escape::encode_safe(&error).to_string()), 61 + }; 62 + 63 + RenderHtml( 64 + "captcha.hbs", 65 + state.template_engine, 66 + CaptchaPage { 67 + handle: params.handle, 68 + state: params.state, 69 + captcha_site_key: hcaptcha_site_key, 70 + error_message, 71 + pds: state.app_config.pds_service_did.replace("did:web:", ""), 72 + redirect_url: params.redirect_url, 73 + }, 74 + ) 75 + .into_response() 76 + } 77 + 78 + /// POST /gate - Verify captcha and redirect 79 + pub async fn post_gate( 80 + State(state): State<AppState>, 81 + Query(params): Query<GateQuery>, 82 + Form(form): Form<CaptchaForm>, 83 + ) -> Response { 84 + // Verify hCaptcha response 85 + let hcaptcha_secret = match env::var("PDS_HCAPTCHA_SECRET_KEY") { 86 + Ok(secret) => secret, 87 + Err(_) => { 88 + return json_error_response( 89 + StatusCode::INTERNAL_SERVER_ERROR, 90 + "ServerError", 91 + "hCaptcha is not configured", 92 + ) 93 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 94 + } 95 + }; 96 + 97 + let client = match reqwest::Client::builder() 98 + .timeout(std::time::Duration::from_secs(10)) 99 + .build() 100 + { 101 + Ok(c) => c, 102 + Err(e) => { 103 + log::error!("Failed to create HTTP client: {}", e); 104 + return json_error_response( 105 + StatusCode::INTERNAL_SERVER_ERROR, 106 + "ServerError", 107 + "Failed to verify captcha", 108 + ) 109 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 110 + } 111 + }; 112 + 113 + #[derive(Deserialize, Serialize)] 114 + struct HCaptchaResponse { 115 + success: bool, 116 + challenge_ts: DateTime<Utc>, 117 + hostname: String, 118 + #[serde(rename = "error-codes", default)] 119 + error_codes: Vec<String>, 120 + } 121 + 122 + let verification_result = client 123 + .post("https://api.hcaptcha.com/siteverify") 124 + .form(&[ 125 + ("secret", hcaptcha_secret.as_str()), 126 + ("response", form.h_captcha_response.as_str()), 127 + ]) 128 + .send() 129 + .await; 130 + 131 + let verification_response = match verification_result { 132 + Ok(resp) => resp, 133 + Err(e) => { 134 + log::error!("Failed to verify hCaptcha: {}", e); 135 + 136 + return Redirect::to(&format!( 137 + "/gate?handle={}&state={}&error={}", 138 + url_encode(&params.handle), 139 + url_encode(&params.state), 140 + url_encode("Verification failed. Please try again.") 141 + )) 142 + .into_response(); 143 + } 144 + }; 145 + 146 + let captcha_result: HCaptchaResponse = match verification_response.json().await { 147 + Ok(result) => result, 148 + Err(e) => { 149 + log::error!("Failed to parse hCaptcha response: {}", e); 150 + 151 + return Redirect::to(&format!( 152 + "/gate?handle={}&state={}&error={}", 153 + url_encode(&params.handle), 154 + url_encode(&params.state), 155 + url_encode("Verification failed. Please try again.") 156 + )) 157 + .into_response(); 158 + } 159 + }; 160 + 161 + if !captcha_result.success { 162 + log::warn!( 163 + "hCaptcha verification failed for handle {}: {:?}", 164 + params.handle, 165 + captcha_result.error_codes 166 + ); 167 + return Redirect::to(&format!( 168 + "/gate?handle={}&state={}&error={}", 169 + url_encode(&params.handle), 170 + url_encode(&params.state), 171 + url_encode("Verification failed. Please try again.") 172 + )) 173 + .into_response(); 174 + } 175 + 176 + // Generate secure JWE verification token 177 + let code = match generate_gate_token(&params.handle, &state.app_config.gate_jwe_key) { 178 + Ok(token) => token, 179 + Err(e) => { 180 + log::error!("Failed to generate gate token: {}", e); 181 + return json_error_response( 182 + StatusCode::INTERNAL_SERVER_ERROR, 183 + "ServerError", 184 + "Failed to create verification code", 185 + ) 186 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 187 + } 188 + }; 189 + 190 + let now = Utc::now(); 191 + 192 + // Store the encrypted token in the database 193 + let result = sqlx::query( 194 + "INSERT INTO gate_codes (code, handle, created_at) 195 + VALUES (?, ?, ?)", 196 + ) 197 + .bind(&code) 198 + .bind(&params.handle) 199 + .bind(now) 200 + .execute(&state.pds_gatekeeper_pool) 201 + .await; 202 + 203 + if let Err(e) = result { 204 + log::error!("Failed to store gate code: {}", e); 205 + return json_error_response( 206 + StatusCode::INTERNAL_SERVER_ERROR, 207 + "ServerError", 208 + "Failed to create verification code", 209 + ) 210 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 211 + } 212 + 213 + // Redirects by origin if it's found. If not redirect to the configured URL. 214 + let mut base_redirect = state.app_config.default_successful_redirect_url.clone(); 215 + if let Some(ref redirect_url) = form.redirect_url { 216 + let trimmed = redirect_url.trim(); 217 + if !trimmed.is_empty() 218 + && (trimmed.starts_with("https://") || trimmed.starts_with("http://")) 219 + { 220 + base_redirect = trimmed.trim_end_matches('/').to_string(); 221 + } 222 + } 223 + 224 + let base_redirect = match state 225 + .app_config 226 + .captcha_success_redirects 227 + .contains(&base_redirect) 228 + { 229 + true => base_redirect, 230 + false => state.app_config.default_successful_redirect_url.clone(), 231 + }; 232 + 233 + // Redirect to client app with code and state 234 + let redirect_url = format!( 235 + "{}/?code={}&state={}", 236 + base_redirect, 237 + url_encode(&code), 238 + url_encode(&params.state) 239 + ); 240 + 241 + Redirect::to(&redirect_url).into_response() 242 + } 243 + 244 + /// Simple URL encode function 245 + fn url_encode(s: &str) -> String { 246 + urlencoding::encode(s).to_string() 247 + }
+177 -14
src/helpers.rs
··· 1 1 use crate::AppState; 2 2 use crate::helpers::TokenCheckError::InvalidToken; 3 3 use anyhow::anyhow; 4 - use axum::body::{Body, to_bytes}; 5 - use axum::extract::Request; 6 - use axum::http::header::CONTENT_TYPE; 7 - use axum::http::{HeaderMap, StatusCode, Uri}; 8 - use axum::response::{IntoResponse, Response}; 4 + use axum::{ 5 + body::{Body, to_bytes}, 6 + extract::Request, 7 + http::header::CONTENT_TYPE, 8 + http::{HeaderMap, StatusCode, Uri}, 9 + response::{IntoResponse, Response}, 10 + }; 9 11 use axum_template::TemplateEngine; 10 12 use chrono::Utc; 11 - use lettre::message::{MultiPart, SinglePart, header}; 12 - use lettre::{AsyncTransport, Message}; 13 + use jacquard_common::{ 14 + service_auth, service_auth::PublicKey, types::did::Did, types::did_doc::VerificationMethod, 15 + types::nsid::Nsid, 16 + }; 17 + use jacquard_identity::{PublicResolver, resolver::IdentityResolver}; 18 + use josekit::jwe::alg::direct::DirectJweAlgorithm; 19 + use lettre::{ 20 + AsyncTransport, Message, 21 + message::{MultiPart, SinglePart, header}, 22 + }; 13 23 use rand::Rng; 14 24 use serde::de::DeserializeOwned; 15 25 use serde_json::{Map, Value}; 16 26 use sha2::{Digest, Sha256}; 17 27 use sqlx::SqlitePool; 28 + use std::sync::Arc; 18 29 use tracing::{error, log}; 19 30 20 31 ///Used to generate the email 2fa code ··· 39 50 where 40 51 T: DeserializeOwned, 41 52 { 42 - let uri = format!("{}{}", state.pds_base_url, path); 53 + let uri = format!("{}{}", state.app_config.pds_base_url, path); 43 54 *req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?; 44 55 45 56 let result = state ··· 134 145 full_code.push(UPPERCASE_BASE32_CHARS[idx] as char); 135 146 } 136 147 137 - //The PDS implementation creates in lowercase, then converts to uppercase. 138 - //Just going a head and doing uppercase here. 139 - let slice_one = &full_code[0..5].to_ascii_uppercase(); 140 - let slice_two = &full_code[5..10].to_ascii_uppercase(); 148 + let slice_one = &full_code[0..5]; 149 + let slice_two = &full_code[5..10]; 141 150 format!("{slice_one}-{slice_two}") 142 151 } 143 152 ··· 337 346 338 347 let email_message = Message::builder() 339 348 //TODO prob get the proper type in the state 340 - .from(state.mailer_from.parse()?) 349 + .from(state.app_config.mailer_from.parse()?) 341 350 .to(email.parse()?) 342 - .subject("Sign in to Bluesky") 351 + .subject(&state.app_config.email_subject) 343 352 .multipart( 344 353 MultiPart::alternative() // This is composed of two parts. 345 354 .singlepart( ··· 522 531 523 532 format!("{masked_local}@{masked_domain}") 524 533 } 534 + 535 + pub enum VerifyServiceAuthError { 536 + AuthFailed, 537 + Error(anyhow::Error), 538 + } 539 + 540 + /// Verifies the service auth token that is appended to an XRPC proxy request 541 + pub async fn verify_service_auth( 542 + jwt: &str, 543 + lxm: &Nsid<'static>, 544 + public_resolver: Arc<PublicResolver>, 545 + service_did: &Did<'static>, 546 + //The did of the user wanting to create an account 547 + requested_did: &Did<'static>, 548 + ) -> Result<(), VerifyServiceAuthError> { 549 + let parsed = 550 + service_auth::parse_jwt(jwt).map_err(|e| VerifyServiceAuthError::Error(e.into()))?; 551 + 552 + let claims = parsed.claims(); 553 + 554 + let did_doc = public_resolver 555 + .resolve_did_doc(&requested_did) 556 + .await 557 + .map_err(|err| { 558 + log::error!("Error resolving the service auth for: {}", claims.iss); 559 + return VerifyServiceAuthError::Error(err.into()); 560 + })?; 561 + 562 + // Parse the DID document response to get verification methods 563 + let doc = did_doc.parse().map_err(|err| { 564 + log::error!("Error parsing the service auth did doc: {}", claims.iss); 565 + VerifyServiceAuthError::Error(anyhow::anyhow!(err)) 566 + })?; 567 + 568 + let verification_methods = doc.verification_method.as_deref().ok_or_else(|| { 569 + VerifyServiceAuthError::Error(anyhow::anyhow!( 570 + "No verification methods in did doc: {}", 571 + &claims.iss 572 + )) 573 + })?; 574 + 575 + let signing_key = extract_signing_key(verification_methods).ok_or_else(|| { 576 + VerifyServiceAuthError::Error(anyhow::anyhow!( 577 + "No signing key found in did doc: {}", 578 + &claims.iss 579 + )) 580 + })?; 581 + 582 + service_auth::verify_signature(&parsed, &signing_key).map_err(|err| { 583 + log::error!("Error verifying service auth signature: {}", err); 584 + VerifyServiceAuthError::AuthFailed 585 + })?; 586 + 587 + // Now validate claims (audience, expiration, etc.) 588 + claims.validate(service_did).map_err(|e| { 589 + log::error!("Error validating service auth claims: {}", e); 590 + VerifyServiceAuthError::AuthFailed 591 + })?; 592 + 593 + if claims.aud != *service_did { 594 + log::error!("Invalid audience (did:web): {}", claims.aud); 595 + return Err(VerifyServiceAuthError::AuthFailed); 596 + } 597 + 598 + let lxm_from_claims = claims.lxm.as_ref().ok_or_else(|| { 599 + VerifyServiceAuthError::Error(anyhow::anyhow!("No lxm claim in service auth JWT")) 600 + })?; 601 + 602 + if lxm_from_claims != lxm { 603 + return Err(VerifyServiceAuthError::Error(anyhow::anyhow!( 604 + "Invalid XRPC endpoint requested" 605 + ))); 606 + } 607 + Ok(()) 608 + } 609 + 610 + /// Ripped from Jacquard 611 + /// 612 + /// Extract the signing key from a DID document's verification methods. 613 + /// 614 + /// This looks for a key with type "atproto" or the first available key 615 + /// if no atproto-specific key is found. 616 + fn extract_signing_key(methods: &[VerificationMethod]) -> Option<PublicKey> { 617 + // First try to find an atproto-specific key 618 + let atproto_method = methods 619 + .iter() 620 + .find(|m| m.r#type.as_ref() == "Multikey" || m.r#type.as_ref() == "atproto"); 621 + 622 + let method = atproto_method.or_else(|| methods.first())?; 623 + 624 + // Parse the multikey 625 + let public_key_multibase = method.public_key_multibase.as_ref()?; 626 + 627 + // Decode multibase 628 + let (_, key_bytes) = multibase::decode(public_key_multibase.as_ref()).ok()?; 629 + 630 + // First two bytes are the multicodec prefix 631 + if key_bytes.len() < 2 { 632 + return None; 633 + } 634 + 635 + let codec = &key_bytes[..2]; 636 + let key_material = &key_bytes[2..]; 637 + 638 + match codec { 639 + // p256-pub (0x1200) 640 + [0x80, 0x24] => PublicKey::from_p256_bytes(key_material).ok(), 641 + // secp256k1-pub (0xe7) 642 + [0xe7, 0x01] => PublicKey::from_k256_bytes(key_material).ok(), 643 + _ => None, 644 + } 645 + } 646 + 647 + /// Payload for gate JWE tokens 648 + #[derive(serde::Serialize, serde::Deserialize, Debug)] 649 + pub struct GateTokenPayload { 650 + pub handle: String, 651 + pub created_at: String, 652 + } 653 + 654 + /// Generate a secure JWE token for gate verification 655 + pub fn generate_gate_token(handle: &str, encryption_key: &[u8]) -> Result<String, anyhow::Error> { 656 + use josekit::jwe::{JweHeader, alg::direct::DirectJweAlgorithm}; 657 + 658 + let payload = GateTokenPayload { 659 + handle: handle.to_string(), 660 + created_at: Utc::now().to_rfc3339(), 661 + }; 662 + 663 + let payload_json = serde_json::to_string(&payload)?; 664 + 665 + let mut header = JweHeader::new(); 666 + header.set_token_type("JWT"); 667 + header.set_content_encryption("A128CBC-HS256"); 668 + 669 + let encrypter = DirectJweAlgorithm::Dir.encrypter_from_bytes(encryption_key)?; 670 + 671 + // Encrypt 672 + let jwe = josekit::jwe::serialize_compact(payload_json.as_bytes(), &header, &encrypter)?; 673 + 674 + Ok(jwe) 675 + } 676 + 677 + /// Verify and decrypt a gate JWE token, returning the payload if valid 678 + pub fn verify_gate_token( 679 + token: &str, 680 + encryption_key: &[u8], 681 + ) -> Result<GateTokenPayload, anyhow::Error> { 682 + let decrypter = DirectJweAlgorithm::Dir.decrypter_from_bytes(encryption_key)?; 683 + let (payload_bytes, _header) = josekit::jwe::deserialize_compact(token, &decrypter)?; 684 + let payload: GateTokenPayload = serde_json::from_slice(&payload_bytes)?; 685 + 686 + Ok(payload) 687 + }
+210 -34
src/main.rs
··· 1 1 #![warn(clippy::unwrap_used)] 2 + use crate::gate::{get_gate, post_gate}; 2 3 use crate::oauth_provider::sign_in; 3 - use crate::xrpc::com_atproto_server::{create_session, get_session, update_email}; 4 - use axum::body::Body; 5 - use axum::handler::Handler; 6 - use axum::http::{Method, header}; 7 - use axum::middleware as ax_middleware; 8 - use axum::routing::post; 9 - use axum::{Router, routing::get}; 4 + use crate::xrpc::com_atproto_server::{ 5 + create_account, create_session, describe_server, get_session, update_email, 6 + }; 7 + use axum::{ 8 + Router, 9 + body::Body, 10 + handler::Handler, 11 + http::{Method, header}, 12 + middleware as ax_middleware, 13 + routing::get, 14 + routing::post, 15 + }; 10 16 use axum_template::engine::Engine; 11 17 use handlebars::Handlebars; 12 - use hyper_util::client::legacy::connect::HttpConnector; 13 - use hyper_util::rt::TokioExecutor; 18 + use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; 19 + use jacquard_common::types::did::Did; 20 + use jacquard_identity::{PublicResolver, resolver::PlcSource}; 14 21 use lettre::{AsyncSmtpTransport, Tokio1Executor}; 22 + use rand::Rng; 15 23 use rust_embed::RustEmbed; 16 24 use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode}; 17 25 use sqlx::{SqlitePool, sqlite::SqlitePoolOptions}; 18 26 use std::path::Path; 27 + use std::sync::Arc; 19 28 use std::time::Duration; 20 29 use std::{env, net::SocketAddr}; 21 - use tower_governor::GovernorLayer; 22 - use tower_governor::governor::GovernorConfigBuilder; 23 - use tower_http::compression::CompressionLayer; 24 - use tower_http::cors::{Any, CorsLayer}; 30 + use tower_governor::{ 31 + GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, 32 + }; 33 + use tower_http::{ 34 + compression::CompressionLayer, 35 + cors::{Any, CorsLayer}, 36 + }; 25 37 use tracing::log; 26 38 use tracing_subscriber::{EnvFilter, fmt, prelude::*}; 27 39 40 + mod gate; 28 41 pub mod helpers; 29 42 mod middleware; 30 43 mod oauth_provider; ··· 37 50 #[include = "*.hbs"] 38 51 struct EmailTemplates; 39 52 53 + #[derive(RustEmbed)] 54 + #[folder = "html_templates"] 55 + #[include = "*.hbs"] 56 + struct HtmlTemplates; 57 + 58 + /// Mostly the env variables that are used in the app 59 + #[derive(Clone, Debug)] 60 + pub struct AppConfig { 61 + pds_base_url: String, 62 + mailer_from: String, 63 + email_subject: String, 64 + allow_only_migrations: bool, 65 + use_captcha: bool, 66 + //The url to redirect to after a successful captcha. Defaults to https://bsky.app, but you may have another social-app fork you rather your users use 67 + //that need to capture this redirect url for creating an account 68 + default_successful_redirect_url: String, 69 + pds_service_did: Did<'static>, 70 + gate_jwe_key: Vec<u8>, 71 + captcha_success_redirects: Vec<String>, 72 + } 73 + 74 + impl AppConfig { 75 + pub fn new() -> Self { 76 + let pds_base_url = 77 + env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string()); 78 + let mailer_from = env::var("PDS_EMAIL_FROM_ADDRESS") 79 + .expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file"); 80 + //Hack not my favorite, but it does work 81 + let allow_only_migrations = env::var("GATEKEEPER_ALLOW_ONLY_MIGRATIONS") 82 + .map(|val| val.parse::<bool>().unwrap_or(false)) 83 + .unwrap_or(false); 84 + 85 + let use_captcha = env::var("GATEKEEPER_CREATE_ACCOUNT_CAPTCHA") 86 + .map(|val| val.parse::<bool>().unwrap_or(false)) 87 + .unwrap_or(false); 88 + 89 + // PDS_SERVICE_DID is the did:web if set, if not it's PDS_HOSTNAME 90 + let pds_service_did = 91 + env::var("PDS_SERVICE_DID").unwrap_or_else(|_| match env::var("PDS_HOSTNAME") { 92 + Ok(pds_hostname) => format!("did:web:{}", pds_hostname), 93 + Err(_) => { 94 + panic!("PDS_HOSTNAME or PDS_SERVICE_DID must be set in your pds.env file") 95 + } 96 + }); 97 + 98 + let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT") 99 + .unwrap_or("Sign in to Bluesky".to_string()); 100 + 101 + // Load or generate JWE encryption key (32 bytes for AES-256) 102 + let gate_jwe_key = env::var("GATEKEEPER_JWE_KEY") 103 + .ok() 104 + .and_then(|key_hex| hex::decode(key_hex).ok()) 105 + .unwrap_or_else(|| { 106 + // Generate a random 32-byte key if not provided 107 + let key: Vec<u8> = (0..32).map(|_| rand::rng().random()).collect(); 108 + log::warn!("WARNING: No GATEKEEPER_JWE_KEY found in the environment. Generated random key (hex): {}", hex::encode(&key)); 109 + log::warn!("This is not strictly needed unless you scale PDS Gatekeeper. Will not also be able to verify tokens between reboots, but they are short lived (5mins)."); 110 + key 111 + }); 112 + 113 + if gate_jwe_key.len() != 32 { 114 + panic!( 115 + "GATEKEEPER_JWE_KEY must be 32 bytes (64 hex characters) for AES-256 encryption" 116 + ); 117 + } 118 + 119 + let captcha_success_redirects = match env::var("GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS") { 120 + Ok(from_env) => from_env.split(",").map(|s| s.trim().to_string()).collect(), 121 + Err(_) => { 122 + vec![ 123 + String::from("https://bsky.app"), 124 + String::from("https://pdsmoover.com"), 125 + String::from("https://blacksky.community"), 126 + String::from("https://tektite.cc"), 127 + ] 128 + } 129 + }; 130 + 131 + AppConfig { 132 + pds_base_url, 133 + mailer_from, 134 + email_subject, 135 + allow_only_migrations, 136 + use_captcha, 137 + default_successful_redirect_url: env::var("GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT") 138 + .unwrap_or("https://bsky.app".to_string()), 139 + pds_service_did: pds_service_did 140 + .parse() 141 + .expect("PDS_SERVICE_DID is not a valid did or could not infer from PDS_HOSTNAME"), 142 + gate_jwe_key, 143 + captcha_success_redirects, 144 + } 145 + } 146 + } 147 + 40 148 #[derive(Clone)] 41 149 pub struct AppState { 42 150 account_pool: SqlitePool, 43 151 pds_gatekeeper_pool: SqlitePool, 44 152 reverse_proxy_client: HyperUtilClient, 45 - pds_base_url: String, 46 153 mailer: AsyncSmtpTransport<Tokio1Executor>, 47 - mailer_from: String, 48 154 template_engine: Engine<Handlebars<'static>>, 155 + resolver: Arc<PublicResolver>, 156 + app_config: AppConfig, 49 157 } 50 158 51 159 async fn root_handler() -> impl axum::response::IntoResponse { ··· 88 196 #[tokio::main] 89 197 async fn main() -> Result<(), Box<dyn std::error::Error>> { 90 198 setup_tracing(); 91 - //TODO may need to change where this reads from? Like an env variable for it's location? Or arg? 92 - dotenvy::from_path(Path::new("./pds.env"))?; 93 - let pds_root = env::var("PDS_DATA_DIRECTORY")?; 199 + let pds_env_location = 200 + env::var("PDS_ENV_LOCATION").unwrap_or_else(|_| "/pds/pds.env".to_string()); 201 + 202 + let result_of_finding_pds_env = dotenvy::from_path(Path::new(&pds_env_location)); 203 + if let Err(e) = result_of_finding_pds_env { 204 + log::error!( 205 + "Error loading pds.env file (ignore if you loaded your variables in the environment somehow else): {e}" 206 + ); 207 + } 208 + 209 + let pds_root = 210 + env::var("PDS_DATA_DIRECTORY").expect("PDS_DATA_DIRECTORY is not set in your pds.env file"); 94 211 let account_db_url = format!("{pds_root}/account.sqlite"); 95 212 96 213 let account_options = SqliteConnectOptions::new() ··· 127 244 //Emailer set up 128 245 let smtp_url = 129 246 env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file"); 130 - let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS") 131 - .expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file"); 247 + 132 248 let mailer: AsyncSmtpTransport<Tokio1Executor> = 133 249 AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build(); 134 250 //Email templates setup ··· 144 260 let _ = hbs.register_embed_templates::<EmailTemplates>(); 145 261 } 146 262 147 - let pds_base_url = 148 - env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string()); 263 + let _ = hbs.register_embed_templates::<HtmlTemplates>(); 264 + 265 + //Reads the PLC source from the pds env's or defaults to ol faithful 266 + let plc_source_url = 267 + env::var("PDS_DID_PLC_URL").unwrap_or_else(|_| "https://plc.directory".to_string()); 268 + let plc_source = PlcSource::PlcDirectory { 269 + base: plc_source_url.parse().unwrap(), 270 + }; 271 + let mut resolver = PublicResolver::default(); 272 + resolver = resolver.with_plc_source(plc_source.clone()); 149 273 150 274 let state = AppState { 151 275 account_pool, 152 276 pds_gatekeeper_pool, 153 277 reverse_proxy_client: client, 154 - pds_base_url, 155 278 mailer, 156 - mailer_from: sent_from, 157 279 template_engine: Engine::from(hbs), 280 + resolver: Arc::new(resolver), 281 + app_config: AppConfig::new(), 158 282 }; 159 283 160 284 // Rate limiting 161 285 //Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds. 162 - let create_session_governor_conf = GovernorConfigBuilder::default() 286 + let captcha_governor_conf = GovernorConfigBuilder::default() 163 287 .per_second(60) 164 288 .burst_size(5) 289 + .key_extractor(SmartIpKeyExtractor) 165 290 .finish() 166 - .expect("failed to create governor config. this should not happen and is a bug"); 291 + .expect("failed to create governor config for create session. this should not happen and is a bug"); 167 292 168 293 // Create a second config with the same settings for the other endpoint 169 294 let sign_in_governor_conf = GovernorConfigBuilder::default() 170 295 .per_second(60) 171 296 .burst_size(5) 297 + .key_extractor(SmartIpKeyExtractor) 172 298 .finish() 173 - .expect("failed to create governor config. this should not happen and is a bug"); 299 + .expect( 300 + "failed to create governor config for sign in. this should not happen and is a bug", 301 + ); 174 302 175 - let create_session_governor_limiter = create_session_governor_conf.limiter().clone(); 303 + let create_account_limiter_time: Option<String> = 304 + env::var("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND").ok(); 305 + let create_account_limiter_burst: Option<String> = 306 + env::var("GATEKEEPER_CREATE_ACCOUNT_BURST").ok(); 307 + 308 + //Default should be 608 requests per 5 minutes, PDS is 300 per 500 so will never hit it ideally 309 + let mut create_account_governor_conf = GovernorConfigBuilder::default(); 310 + if create_account_limiter_time.is_some() { 311 + let time = create_account_limiter_time 312 + .expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND not set") 313 + .parse::<u64>() 314 + .expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND must be a valid integer"); 315 + create_account_governor_conf.per_second(time); 316 + } 317 + 318 + if create_account_limiter_burst.is_some() { 319 + let burst = create_account_limiter_burst 320 + .expect("GATEKEEPER_CREATE_ACCOUNT_BURST not set") 321 + .parse::<u32>() 322 + .expect("GATEKEEPER_CREATE_ACCOUNT_BURST must be a valid integer"); 323 + create_account_governor_conf.burst_size(burst); 324 + } 325 + 326 + let create_account_governor_conf = create_account_governor_conf 327 + .key_extractor(SmartIpKeyExtractor) 328 + .finish().expect( 329 + "failed to create governor config for create account. this should not happen and is a bug", 330 + ); 331 + 332 + let captcha_governor_limiter = captcha_governor_conf.limiter().clone(); 176 333 let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone(); 334 + let create_account_governor_limiter = create_account_governor_conf.limiter().clone(); 335 + 336 + let sign_in_governor_layer = GovernorLayer::new(sign_in_governor_conf); 337 + 177 338 let interval = Duration::from_secs(60); 178 339 // a separate background task to clean up 179 340 std::thread::spawn(move || { 180 341 loop { 181 342 std::thread::sleep(interval); 182 - create_session_governor_limiter.retain_recent(); 343 + captcha_governor_limiter.retain_recent(); 183 344 sign_in_governor_limiter.retain_recent(); 345 + create_account_governor_limiter.retain_recent(); 184 346 } 185 347 }); 186 348 ··· 189 351 .allow_methods([Method::GET, Method::OPTIONS, Method::POST]) 190 352 .allow_headers(Any); 191 353 192 - let app = Router::new() 354 + let mut app = Router::new() 193 355 .route("/", get(root_handler)) 356 + .route("/xrpc/com.atproto.server.getSession", get(get_session)) 194 357 .route( 195 - "/xrpc/com.atproto.server.getSession", 196 - get(get_session).layer(ax_middleware::from_fn(middleware::extract_did)), 358 + "/xrpc/com.atproto.server.describeServer", 359 + get(describe_server), 197 360 ) 198 361 .route( 199 362 "/xrpc/com.atproto.server.updateEmail", ··· 201 364 ) 202 365 .route( 203 366 "/@atproto/oauth-provider/~api/sign-in", 204 - post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)), 367 + post(sign_in).layer(sign_in_governor_layer.clone()), 205 368 ) 206 369 .route( 207 370 "/xrpc/com.atproto.server.createSession", 208 - post(create_session.layer(GovernorLayer::new(create_session_governor_conf))), 371 + post(create_session.layer(sign_in_governor_layer)), 209 372 ) 373 + .route( 374 + "/xrpc/com.atproto.server.createAccount", 375 + post(create_account).layer(GovernorLayer::new(create_account_governor_conf)), 376 + ); 377 + 378 + if state.app_config.use_captcha { 379 + app = app.route( 380 + "/gate/signup", 381 + get(get_gate).post(post_gate.layer(GovernorLayer::new(captcha_governor_conf))), 382 + ); 383 + } 384 + 385 + let app = app 210 386 .layer(CompressionLayer::new()) 211 387 .layer(cors) 212 388 .with_state(state);
+73 -39
src/middleware.rs
··· 12 12 #[derive(Clone, Debug)] 13 13 pub struct Did(pub Option<String>); 14 14 15 + #[derive(Clone, Copy, Debug, PartialEq, Eq)] 16 + pub enum AuthScheme { 17 + Bearer, 18 + DPoP, 19 + } 20 + 15 21 #[derive(Serialize, Deserialize)] 16 22 pub struct TokenClaims { 17 23 pub sub: String, 18 24 } 19 25 20 26 pub async fn extract_did(mut req: Request, next: Next) -> impl IntoResponse { 21 - let token = extract_bearer(req.headers()); 27 + let auth = extract_auth(req.headers()); 22 28 23 - match token { 24 - Ok(token) => { 25 - match token { 29 + match auth { 30 + Ok(auth_opt) => { 31 + match auth_opt { 26 32 None => json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "") 27 33 .expect("Error creating an error response"), 28 - Some(token) => { 29 - let token = UntrustedToken::new(&token); 30 - if token.is_err() { 31 - return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "") 32 - .expect("Error creating an error response"); 33 - } 34 - let parsed_token = token.expect("Already checked for error"); 35 - let claims: Result<Claims<TokenClaims>, ValidationError> = 36 - parsed_token.deserialize_claims_unchecked(); 37 - if claims.is_err() { 38 - return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "") 39 - .expect("Error creating an error response"); 40 - } 34 + Some((scheme, token_str)) => { 35 + // For Bearer, validate JWT and extract DID from `sub`. 36 + // For DPoP, we currently only pass through and do not validate here; insert None DID. 37 + match scheme { 38 + AuthScheme::Bearer => { 39 + let token = UntrustedToken::new(&token_str); 40 + if token.is_err() { 41 + return json_error_response( 42 + StatusCode::BAD_REQUEST, 43 + "TokenRequired", 44 + "", 45 + ) 46 + .expect("Error creating an error response"); 47 + } 48 + let parsed_token = token.expect("Already checked for error"); 49 + let claims: Result<Claims<TokenClaims>, ValidationError> = 50 + parsed_token.deserialize_claims_unchecked(); 51 + if claims.is_err() { 52 + return json_error_response( 53 + StatusCode::BAD_REQUEST, 54 + "TokenRequired", 55 + "", 56 + ) 57 + .expect("Error creating an error response"); 58 + } 41 59 42 - let key = Hs256Key::new( 43 - env::var("PDS_JWT_SECRET").expect("PDS_JWT_SECRET not set in the pds.env"), 44 - ); 45 - let token: Result<Token<TokenClaims>, ValidationError> = 46 - Hs256.validator(&key).validate(&parsed_token); 47 - if token.is_err() { 48 - return json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "") 49 - .expect("Error creating an error response"); 60 + let key = Hs256Key::new( 61 + env::var("PDS_JWT_SECRET") 62 + .expect("PDS_JWT_SECRET not set in the pds.env"), 63 + ); 64 + let token: Result<Token<TokenClaims>, ValidationError> = 65 + Hs256.validator(&key).validate(&parsed_token); 66 + if token.is_err() { 67 + return json_error_response( 68 + StatusCode::BAD_REQUEST, 69 + "InvalidToken", 70 + "", 71 + ) 72 + .expect("Error creating an error response"); 73 + } 74 + let token = token.expect("Already checked for error,"); 75 + req.extensions_mut() 76 + .insert(Did(Some(token.claims().custom.sub.clone()))); 77 + } 78 + AuthScheme::DPoP => { 79 + //Not going to worry about oauth email update for now, just always forward to the PDS 80 + req.extensions_mut().insert(Did(None)); 81 + } 50 82 } 51 - let token = token.expect("Already checked for error,"); 52 - //Not going to worry about expiration since it still goes to the PDS 53 - req.extensions_mut() 54 - .insert(Did(Some(token.claims().custom.sub.clone()))); 83 + 55 84 next.run(req).await 56 85 } 57 86 } ··· 64 93 } 65 94 } 66 95 67 - fn extract_bearer(headers: &HeaderMap) -> Result<Option<String>, String> { 96 + fn extract_auth(headers: &HeaderMap) -> Result<Option<(AuthScheme, String)>, String> { 68 97 match headers.get(axum::http::header::AUTHORIZATION) { 69 98 None => Ok(None), 70 - Some(hv) => match hv.to_str() { 71 - Err(_) => Err("Authorization header is not valid".into()), 72 - Ok(s) => { 73 - // Accept forms like: "Bearer <token>" (case-sensitive for the scheme here) 74 - let mut parts = s.splitn(2, ' '); 75 - match (parts.next(), parts.next()) { 76 - (Some("Bearer"), Some(tok)) if !tok.is_empty() => Ok(Some(tok.to_string())), 77 - _ => Err("Authorization header must be in format 'Bearer <token>'".into()), 99 + Some(hv) => { 100 + match hv.to_str() { 101 + Err(_) => Err("Authorization header is not valid".into()), 102 + Ok(s) => { 103 + // Accept forms like: "Bearer <token>" or "DPoP <token>" (case-sensitive for the scheme here) 104 + let mut parts = s.splitn(2, ' '); 105 + match (parts.next(), parts.next()) { 106 + (Some("Bearer"), Some(tok)) if !tok.is_empty() => 107 + Ok(Some((AuthScheme::Bearer, tok.to_string()))), 108 + (Some("DPoP"), Some(tok)) if !tok.is_empty() => 109 + Ok(Some((AuthScheme::DPoP, tok.to_string()))), 110 + _ => Err("Authorization header must be in format 'Bearer <token>' or 'DPoP <token>'".into()), 111 + } 78 112 } 79 113 } 80 - }, 114 + } 81 115 } 82 116 }
+4 -6
src/oauth_provider.rs
··· 13 13 pub struct SignInRequest { 14 14 pub username: String, 15 15 pub password: String, 16 - pub remember: bool, 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + pub remember: Option<bool>, 17 18 pub locale: String, 18 19 #[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")] 19 20 pub email_otp: Option<String>, ··· 36 37 "Invalid identifier or password", 37 38 ), 38 39 AuthResult::TwoFactorRequired(masked_email) => { 39 - // Email sending step can be handled here if needed in the future. 40 - 41 - // {"error":"second_authentication_factor_required","error_description":"emailOtp authentication factor required (hint: 2***0@p***m)","type":"emailOtp","hint":"2***0@p***m"} 42 40 let body_str = match serde_json::to_string(&serde_json::json!({ 43 41 "error": "second_authentication_factor_required", 44 42 "error_description": format!("emailOtp authentication factor required (hint: {})", masked_email), ··· 59 57 //No 2FA or already passed 60 58 let uri = format!( 61 59 "{}{}", 62 - state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in" 60 + state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in" 63 61 ); 64 62 65 63 let mut req = axum::http::Request::post(uri); ··· 97 95 }, 98 96 Err(err) => { 99 97 log::error!( 100 - "Error during pre-auth check. This happens on the create_session endpoint when trying to decide if the user has access:\n {err}" 98 + "Error during pre-auth check. This happens on the oauth signin endpoint when trying to decide if the user has access:\n {err}" 101 99 ); 102 100 oauth_json_error_response( 103 101 StatusCode::BAD_REQUEST,
+397 -53
src/xrpc/com_atproto_server.rs
··· 1 1 use crate::AppState; 2 2 use crate::helpers::{ 3 - AuthResult, ProxiedResult, TokenCheckError, json_error_response, preauth_check, proxy_get_json, 3 + AuthResult, ProxiedResult, TokenCheckError, VerifyServiceAuthError, json_error_response, 4 + preauth_check, proxy_get_json, verify_gate_token, verify_service_auth, 4 5 }; 5 6 use crate::middleware::Did; 6 - use axum::body::Body; 7 + use axum::body::{Body, to_bytes}; 7 8 use axum::extract::State; 8 - use axum::http::{HeaderMap, StatusCode}; 9 + use axum::http::{HeaderMap, StatusCode, header}; 9 10 use axum::response::{IntoResponse, Response}; 10 11 use axum::{Extension, Json, debug_handler, extract, extract::Request}; 12 + use chrono::{Duration, Utc}; 13 + use jacquard_common::types::did::Did as JacquardDid; 11 14 use serde::{Deserialize, Serialize}; 12 15 use serde_json; 13 16 use tracing::log; ··· 61 64 allow_takendown: Option<bool>, 62 65 } 63 66 67 + #[derive(Deserialize, Serialize, Debug)] 68 + #[serde(rename_all = "camelCase")] 69 + pub struct CreateAccountRequest { 70 + handle: String, 71 + #[serde(skip_serializing_if = "Option::is_none")] 72 + email: Option<String>, 73 + #[serde(skip_serializing_if = "Option::is_none")] 74 + password: Option<String>, 75 + #[serde(skip_serializing_if = "Option::is_none")] 76 + did: Option<String>, 77 + #[serde(skip_serializing_if = "Option::is_none")] 78 + invite_code: Option<String>, 79 + #[serde(skip_serializing_if = "Option::is_none")] 80 + verification_code: Option<String>, 81 + #[serde(skip_serializing_if = "Option::is_none")] 82 + plc_op: Option<serde_json::Value>, 83 + } 84 + 85 + #[derive(Deserialize, Serialize, Debug, Clone)] 86 + #[serde(rename_all = "camelCase")] 87 + pub struct DescribeServerContact { 88 + #[serde(skip_serializing_if = "Option::is_none")] 89 + email: Option<String>, 90 + } 91 + 92 + #[derive(Deserialize, Serialize, Debug, Clone)] 93 + #[serde(rename_all = "camelCase")] 94 + pub struct DescribeServerLinks { 95 + #[serde(skip_serializing_if = "Option::is_none")] 96 + privacy_policy: Option<String>, 97 + #[serde(skip_serializing_if = "Option::is_none")] 98 + terms_of_service: Option<String>, 99 + } 100 + 101 + #[derive(Deserialize, Serialize, Debug, Clone)] 102 + #[serde(rename_all = "camelCase")] 103 + pub struct DescribeServerResponse { 104 + #[serde(skip_serializing_if = "Option::is_none")] 105 + invite_code_required: Option<bool>, 106 + #[serde(skip_serializing_if = "Option::is_none")] 107 + phone_verification_required: Option<bool>, 108 + #[serde(skip_serializing_if = "Option::is_none")] 109 + available_user_domains: Option<Vec<String>>, 110 + #[serde(skip_serializing_if = "Option::is_none")] 111 + links: Option<DescribeServerLinks>, 112 + #[serde(skip_serializing_if = "Option::is_none")] 113 + contact: Option<DescribeServerContact>, 114 + #[serde(skip_serializing_if = "Option::is_none")] 115 + did: Option<String>, 116 + } 117 + 64 118 pub async fn create_session( 65 119 State(state): State<AppState>, 66 120 headers: HeaderMap, ··· 87 141 ) 88 142 } 89 143 AuthResult::ProxyThrough => { 90 - log::info!("Proxying through"); 91 144 //No 2FA or already passed 92 145 let uri = format!( 93 146 "{}{}", 94 - state.pds_base_url, "/xrpc/com.atproto.server.createSession" 147 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession" 95 148 ); 96 149 97 150 let mut req = axum::http::Request::post(uri); ··· 148 201 //If email auth is set it is to either turn on or off 2fa 149 202 let email_auth_update = payload.email_auth_factor.unwrap_or(false); 150 203 151 - // Email update asked for 152 - if email_auth_update { 153 - let email = payload.email.clone(); 154 - let email_confirmed = sqlx::query_as::<_, (String,)>( 155 - "SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?", 156 - ) 157 - .bind(&email) 158 - .fetch_optional(&state.account_pool) 159 - .await 160 - .map_err(|_| StatusCode::BAD_REQUEST)?; 161 - 162 - //Since the email is already confirmed we can enable 2fa 163 - return match email_confirmed { 164 - None => Err(StatusCode::BAD_REQUEST), 165 - Some(did_row) => { 166 - let _ = sqlx::query( 167 - "INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1", 168 - ) 169 - .bind(&did_row.0) 170 - .execute(&state.pds_gatekeeper_pool) 171 - .await 172 - .map_err(|_| StatusCode::BAD_REQUEST)?; 173 - 174 - Ok(StatusCode::OK.into_response()) 175 - } 176 - }; 177 - } 204 + //This means the middleware successfully extracted a did from the request, if not it just needs to be forward to the PDS 205 + //This is also empty if it is an oauth request, which is not supported by gatekeeper turning on 2fa since the dpop stuff needs to be implemented 206 + let did_is_not_empty = did.0.is_some(); 178 207 179 - // User wants auth turned off 180 - if !email_auth_update && !email_auth_not_set { 181 - //User wants auth turned off and has a token 182 - if let Some(token) = &payload.token { 183 - let token_found = sqlx::query_as::<_, (String,)>( 184 - "SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'", 208 + if did_is_not_empty { 209 + // Email update asked for 210 + if email_auth_update { 211 + let email = payload.email.clone(); 212 + let email_confirmed = match sqlx::query_as::<_, (String,)>( 213 + "SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?", 185 214 ) 186 - .bind(token) 187 - .bind(&did.0) 215 + .bind(&email) 188 216 .fetch_optional(&state.account_pool) 189 217 .await 190 - .map_err(|_| StatusCode::BAD_REQUEST)?; 218 + { 219 + Ok(row) => row, 220 + Err(err) => { 221 + log::error!("Error checking if email is confirmed: {err}"); 222 + return Err(StatusCode::BAD_REQUEST); 223 + } 224 + }; 191 225 192 - if token_found.is_some() { 193 - let _ = sqlx::query( 194 - "INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0", 226 + //Since the email is already confirmed we can enable 2fa 227 + return match email_confirmed { 228 + None => Err(StatusCode::BAD_REQUEST), 229 + Some(did_row) => { 230 + let _ = sqlx::query( 231 + "INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1", 232 + ) 233 + .bind(&did_row.0) 234 + .execute(&state.pds_gatekeeper_pool) 235 + .await 236 + .map_err(|err| { 237 + log::error!("Error enabling 2FA: {err}"); 238 + StatusCode::BAD_REQUEST 239 + })?; 240 + 241 + Ok(StatusCode::OK.into_response()) 242 + } 243 + }; 244 + } 245 + 246 + // User wants auth turned off 247 + if !email_auth_update && !email_auth_not_set { 248 + //User wants auth turned off and has a token 249 + if let Some(token) = &payload.token { 250 + let token_found = match sqlx::query_as::<_, (String,)>( 251 + "SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'", 195 252 ) 196 - .bind(&did.0) 197 - .execute(&state.pds_gatekeeper_pool) 198 - .await 199 - .map_err(|_| StatusCode::BAD_REQUEST)?; 253 + .bind(token) 254 + .bind(&did.0) 255 + .fetch_optional(&state.account_pool) 256 + .await{ 257 + Ok(token) => token, 258 + Err(err) => { 259 + log::error!("Error checking if token is valid: {err}"); 260 + return Err(StatusCode::BAD_REQUEST); 261 + } 262 + }; 200 263 201 - return Ok(StatusCode::OK.into_response()); 202 - } else { 203 - return Err(StatusCode::BAD_REQUEST); 264 + return if token_found.is_some() { 265 + //TODO I think there may be a bug here and need to do some retry logic 266 + // First try was erroring, seconds was allowing 267 + match sqlx::query( 268 + "INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0", 269 + ) 270 + .bind(&did.0) 271 + .execute(&state.pds_gatekeeper_pool) 272 + .await { 273 + Ok(_) => {} 274 + Err(err) => { 275 + log::error!("Error updating email auth: {err}"); 276 + return Err(StatusCode::BAD_REQUEST); 277 + } 278 + } 279 + 280 + Ok(StatusCode::OK.into_response()) 281 + } else { 282 + Err(StatusCode::BAD_REQUEST) 283 + }; 204 284 } 205 285 } 206 286 } 207 - 208 287 // Updating the actual email address by sending it on to the PDS 209 288 let uri = format!( 210 289 "{}{}", 211 - state.pds_base_url, "/xrpc/com.atproto.server.updateEmail" 290 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail" 212 291 ); 213 292 let mut req = axum::http::Request::post(uri); 214 293 if let Some(req_headers) = req.headers_mut() { ··· 260 339 ProxiedResult::Passthrough(resp) => Ok(resp), 261 340 } 262 341 } 342 + 343 + pub async fn describe_server( 344 + State(state): State<AppState>, 345 + req: Request, 346 + ) -> Result<Response<Body>, StatusCode> { 347 + match proxy_get_json::<DescribeServerResponse>( 348 + &state, 349 + req, 350 + "/xrpc/com.atproto.server.describeServer", 351 + ) 352 + .await? 353 + { 354 + ProxiedResult::Parsed { 355 + value: mut server_info, 356 + .. 357 + } => { 358 + //This signifies the server is configured for captcha verification 359 + server_info.phone_verification_required = Some(state.app_config.use_captcha); 360 + Ok(Json(server_info).into_response()) 361 + } 362 + ProxiedResult::Passthrough(resp) => Ok(resp), 363 + } 364 + } 365 + 366 + /// Verify a gate code matches the handle and is not expired 367 + async fn verify_gate_code( 368 + state: &AppState, 369 + code: &str, 370 + handle: &str, 371 + ) -> Result<bool, anyhow::Error> { 372 + // First, decrypt and verify the JWE token 373 + let payload = match verify_gate_token(code, &state.app_config.gate_jwe_key) { 374 + Ok(p) => p, 375 + Err(e) => { 376 + log::warn!("Failed to decrypt gate token: {}", e); 377 + return Ok(false); 378 + } 379 + }; 380 + 381 + // Verify the handle matches 382 + if payload.handle != handle { 383 + log::warn!( 384 + "Gate code handle mismatch: expected {}, got {}", 385 + handle, 386 + payload.handle 387 + ); 388 + return Ok(false); 389 + } 390 + 391 + let created_at = chrono::DateTime::parse_from_rfc3339(&payload.created_at) 392 + .map_err(|e| anyhow::anyhow!("Failed to parse created_at from token: {}", e))? 393 + .with_timezone(&Utc); 394 + 395 + let now = Utc::now(); 396 + let age = now - created_at; 397 + 398 + // Check if the token is expired (5 minutes) 399 + if age > Duration::minutes(5) { 400 + log::warn!("Gate code expired for handle {}", handle); 401 + return Ok(false); 402 + } 403 + 404 + // Verify the token exists in the database (to prevent reuse) 405 + let row: Option<(String,)> = 406 + sqlx::query_as("SELECT code FROM gate_codes WHERE code = ? and handle = ? LIMIT 1") 407 + .bind(code) 408 + .bind(handle) 409 + .fetch_optional(&state.pds_gatekeeper_pool) 410 + .await?; 411 + 412 + if row.is_none() { 413 + log::warn!("Gate code not found in database or already used"); 414 + return Ok(false); 415 + } 416 + 417 + // Token is valid, delete it so it can't be reused 418 + //TODO probably also delete expired codes? Will need to do that at some point probably altho the where is on code and handle 419 + 420 + sqlx::query("DELETE FROM gate_codes WHERE code = ?") 421 + .bind(code) 422 + .execute(&state.pds_gatekeeper_pool) 423 + .await?; 424 + 425 + Ok(true) 426 + } 427 + 428 + pub async fn create_account( 429 + State(state): State<AppState>, 430 + req: Request, 431 + ) -> Result<Response<Body>, StatusCode> { 432 + let headers = req.headers().clone(); 433 + let body_bytes = to_bytes(req.into_body(), usize::MAX) 434 + .await 435 + .map_err(|_| StatusCode::BAD_REQUEST)?; 436 + 437 + // Parse the body to check for verification code 438 + let account_request: CreateAccountRequest = 439 + serde_json::from_slice(&body_bytes).map_err(|e| { 440 + log::error!("Failed to parse create account request: {}", e); 441 + StatusCode::BAD_REQUEST 442 + })?; 443 + 444 + // Check for service auth (migrations) if configured 445 + if state.app_config.allow_only_migrations { 446 + // Expect Authorization: Bearer <jwt> 447 + let auth_header = headers 448 + .get(header::AUTHORIZATION) 449 + .and_then(|v| v.to_str().ok()) 450 + .map(str::to_string); 451 + 452 + let Some(value) = auth_header else { 453 + log::error!("No Authorization header found in the request"); 454 + return json_error_response( 455 + StatusCode::UNAUTHORIZED, 456 + "InvalidAuth", 457 + "This PDS is configured to only allow accounts created by migrations via this endpoint.", 458 + ); 459 + }; 460 + 461 + // Ensure Bearer prefix 462 + let token = value.strip_prefix("Bearer ").unwrap_or("").trim(); 463 + if token.is_empty() { 464 + log::error!("No Service Auth token found in the Authorization header"); 465 + return json_error_response( 466 + StatusCode::UNAUTHORIZED, 467 + "InvalidAuth", 468 + "This PDS is configured to only allow accounts created by migrations via this endpoint.", 469 + ); 470 + } 471 + 472 + // Ensure a non-empty DID was provided when migrations are enabled 473 + let requested_did_str = match account_request.did.as_deref() { 474 + Some(s) if !s.trim().is_empty() => s, 475 + _ => { 476 + return json_error_response( 477 + StatusCode::BAD_REQUEST, 478 + "InvalidRequest", 479 + "The 'did' field is required when migrations are enforced.", 480 + ); 481 + } 482 + }; 483 + 484 + // Parse the DID into the expected type for verification 485 + let requested_did: JacquardDid<'static> = match requested_did_str.parse() { 486 + Ok(d) => d, 487 + Err(e) => { 488 + log::error!( 489 + "Invalid DID format provided in createAccount: {} | error: {}", 490 + requested_did_str, 491 + e 492 + ); 493 + return json_error_response( 494 + StatusCode::BAD_REQUEST, 495 + "InvalidRequest", 496 + "The 'did' field is not a valid DID.", 497 + ); 498 + } 499 + }; 500 + 501 + let nsid = "com.atproto.server.createAccount".parse().unwrap(); 502 + match verify_service_auth( 503 + token, 504 + &nsid, 505 + state.resolver.clone(), 506 + &state.app_config.pds_service_did, 507 + &requested_did, 508 + ) 509 + .await 510 + { 511 + //Just do nothing if it passes so it continues. 512 + Ok(_) => {} 513 + Err(err) => match err { 514 + VerifyServiceAuthError::AuthFailed => { 515 + return json_error_response( 516 + StatusCode::UNAUTHORIZED, 517 + "InvalidAuth", 518 + "This PDS is configured to only allow accounts created by migrations via this endpoint.", 519 + ); 520 + } 521 + VerifyServiceAuthError::Error(err) => { 522 + log::error!("Error verifying service auth token: {err}"); 523 + return json_error_response( 524 + StatusCode::BAD_REQUEST, 525 + "InvalidRequest", 526 + "There has been an error, please contact your PDS administrator for help and for them to review the server logs.", 527 + ); 528 + } 529 + }, 530 + } 531 + } 532 + 533 + // Check for captcha verification if configured 534 + if state.app_config.use_captcha { 535 + if let Some(ref verification_code) = account_request.verification_code { 536 + match verify_gate_code(&state, verification_code, &account_request.handle).await { 537 + //TODO has a few errors to support 538 + 539 + //expired token 540 + // { 541 + // "error": "ExpiredToken", 542 + // "message": "Token has expired" 543 + // } 544 + 545 + //TODO ALSO add rate limits on the /gate endpoints so they can't be abused 546 + Ok(true) => { 547 + log::info!("Gate code verified for handle: {}", account_request.handle); 548 + } 549 + Ok(false) => { 550 + log::warn!( 551 + "Invalid or expired gate code for handle: {}", 552 + account_request.handle 553 + ); 554 + return json_error_response( 555 + StatusCode::BAD_REQUEST, 556 + "InvalidToken", 557 + "Token could not be verified", 558 + ); 559 + } 560 + Err(e) => { 561 + log::error!("Error verifying gate code: {}", e); 562 + return json_error_response( 563 + StatusCode::INTERNAL_SERVER_ERROR, 564 + "InvalidToken", 565 + "Token could not be verified", 566 + ); 567 + } 568 + } 569 + } else { 570 + // No verification code provided but captcha is required 571 + log::warn!( 572 + "No verification code provided for account creation: {}", 573 + account_request.handle 574 + ); 575 + return json_error_response( 576 + StatusCode::BAD_REQUEST, 577 + "InvalidRequest", 578 + "Verification is now required on this server.", 579 + ); 580 + } 581 + } 582 + 583 + // Rebuild the request with the same body and headers 584 + let uri = format!( 585 + "{}{}", 586 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.createAccount" 587 + ); 588 + 589 + let mut new_req = axum::http::Request::post(&uri); 590 + if let Some(req_headers) = new_req.headers_mut() { 591 + *req_headers = headers; 592 + } 593 + 594 + let new_req = new_req 595 + .body(Body::from(body_bytes)) 596 + .map_err(|_| StatusCode::BAD_REQUEST)?; 597 + 598 + let proxied = state 599 + .reverse_proxy_client 600 + .request(new_req) 601 + .await 602 + .map_err(|_| StatusCode::BAD_REQUEST)? 603 + .into_response(); 604 + 605 + Ok(proxied) 606 + }