Microservice to bring 2FA to self hosted PDSes

Feature: Captcha and Migrations only blocker

Adds two new features to allow PDS admins to gatekeep their PDS from bots creating accounts instead of invite codes

+1398 -84
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]] 61 + name = "aliasable" 62 + version = "0.1.3" 63 + source = "registry+https://github.com/rust-lang/crates.io-index" 64 + checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 65 + 66 + [[package]] 42 67 name = "allocator-api2" 43 68 version = "0.2.21" 44 69 source = "registry+https://github.com/rust-lang/crates.io-index" 45 70 checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 46 71 47 72 [[package]] 48 - name = "android-tzdata" 49 - version = "0.1.1" 50 - source = "registry+https://github.com/rust-lang/crates.io-index" 51 - checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 52 - 53 - [[package]] 54 73 name = "android_system_properties" 55 74 version = "0.1.5" 56 75 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 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]] ··· 198 218 dependencies = [ 199 219 "proc-macro2", 200 220 "quote", 201 - "syn", 221 + "syn 2.0.105", 202 222 ] 203 223 204 224 [[package]] ··· 229 249 ] 230 250 231 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]] 232 274 name = "base64" 233 275 version = "0.22.1" 234 276 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 257 299 "proc-macro2", 258 300 "quote", 259 301 "regex", 260 - "rustc-hash", 302 + "rustc-hash 1.1.0", 261 303 "shlex", 262 - "syn", 304 + "syn 2.0.105", 263 305 "which", 264 306 ] 265 307 ··· 282 324 ] 283 325 284 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]] 285 361 name = "bstr" 286 362 version = "1.12.0" 287 363 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 292 368 ] 293 369 294 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]] 295 395 name = "bumpalo" 296 396 version = "3.19.0" 297 397 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 308 408 version = "1.10.1" 309 409 source = "registry+https://github.com/rust-lang/crates.io-index" 310 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 + ] 311 423 312 424 [[package]] 313 425 name = "cc" ··· 321 433 ] 322 434 323 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]] 324 445 name = "cexpr" 325 446 version = "0.6.0" 326 447 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 336 457 checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 337 458 338 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]] 339 466 name = "chrono" 340 - version = "0.4.41" 467 + version = "0.4.42" 341 468 source = "registry+https://github.com/rust-lang/crates.io-index" 342 - checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 469 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 343 470 dependencies = [ 344 - "android-tzdata", 345 471 "iana-time-zone", 346 472 "js-sys", 347 473 "num-traits", 474 + "serde", 348 475 "wasm-bindgen", 349 - "windows-link", 476 + "windows-link 0.2.1", 350 477 ] 351 478 352 479 [[package]] ··· 387 514 ] 388 515 389 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]] 390 531 name = "cipher" 391 532 version = "0.4.4" 392 533 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 432 573 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 433 574 434 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]] 582 + name = "core-foundation" 583 + version = "0.9.4" 584 + source = "registry+https://github.com/rust-lang/crates.io-index" 585 + checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 586 + dependencies = [ 587 + "core-foundation-sys", 588 + "libc", 589 + ] 590 + 591 + [[package]] 435 592 name = "core-foundation-sys" 436 593 version = "0.8.7" 437 594 source = "registry+https://github.com/rust-lang/crates.io-index" 438 595 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 439 596 440 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]] 441 607 name = "cpufeatures" 442 608 version = "0.2.17" 443 609 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 462 628 checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 463 629 464 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]] 465 640 name = "crossbeam-queue" 466 641 version = "0.3.12" 467 642 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 483 658 checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 484 659 485 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]] 486 673 name = "crypto-common" 487 674 version = "0.1.6" 488 675 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 498 685 source = "registry+https://github.com/rust-lang/crates.io-index" 499 686 checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 500 687 dependencies = [ 501 - "darling_core", 502 - "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", 503 700 ] 504 701 505 702 [[package]] ··· 513 710 "proc-macro2", 514 711 "quote", 515 712 "strsim", 516 - "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", 517 728 ] 518 729 519 730 [[package]] ··· 522 733 source = "registry+https://github.com/rust-lang/crates.io-index" 523 734 checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 524 735 dependencies = [ 525 - "darling_core", 736 + "darling_core 0.20.11", 526 737 "quote", 527 - "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", 528 750 ] 529 751 530 752 [[package]] ··· 542 764 ] 543 765 544 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]] 545 793 name = "der" 546 794 version = "0.7.10" 547 795 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 553 801 ] 554 802 555 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]] 556 814 name = "derive_builder" 557 815 version = "0.20.2" 558 816 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 567 825 source = "registry+https://github.com/rust-lang/crates.io-index" 568 826 checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 569 827 dependencies = [ 570 - "darling", 828 + "darling 0.20.11", 571 829 "proc-macro2", 572 830 "quote", 573 - "syn", 831 + "syn 2.0.105", 574 832 ] 575 833 576 834 [[package]] ··· 580 838 checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 581 839 dependencies = [ 582 840 "derive_builder_core", 583 - "syn", 841 + "syn 2.0.105", 584 842 ] 585 843 586 844 [[package]] ··· 603 861 dependencies = [ 604 862 "proc-macro2", 605 863 "quote", 606 - "syn", 864 + "syn 2.0.105", 607 865 ] 608 866 609 867 [[package]] ··· 619 877 checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 620 878 621 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]] 622 900 name = "either" 623 901 version = "1.15.0" 624 902 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 628 906 ] 629 907 630 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", 926 + ] 927 + 928 + [[package]] 631 929 name = "email-encoding" 632 930 version = "0.4.1" 633 931 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 644 942 checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" 645 943 646 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]] 647 954 name = "equivalent" 648 955 version = "1.0.2" 649 956 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 688 995 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 689 996 690 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]] 691 1018 name = "flume" 692 1019 version = "0.11.1" 693 1020 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 709 1036 version = "0.1.5" 710 1037 source = "registry+https://github.com/rust-lang/crates.io-index" 711 1038 checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 1039 + 1040 + [[package]] 1041 + name = "foreign-types" 1042 + version = "0.3.2" 1043 + source = "registry+https://github.com/rust-lang/crates.io-index" 1044 + checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 1045 + dependencies = [ 1046 + "foreign-types-shared", 1047 + ] 1048 + 1049 + [[package]] 1050 + name = "foreign-types-shared" 1051 + version = "0.1.1" 1052 + source = "registry+https://github.com/rust-lang/crates.io-index" 1053 + checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 712 1054 713 1055 [[package]] 714 1056 name = "form_urlencoded" ··· 780 1122 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 781 1123 782 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]] 783 1136 name = "futures-sink" 784 1137 version = "0.3.31" 785 1138 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 805 1158 dependencies = [ 806 1159 "futures-core", 807 1160 "futures-io", 1161 + "futures-macro", 808 1162 "futures-sink", 809 1163 "futures-task", 810 1164 "memchr", ··· 821 1175 dependencies = [ 822 1176 "typenum", 823 1177 "version_check", 1178 + "zeroize", 824 1179 ] 825 1180 826 1181 [[package]] ··· 830 1185 checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 831 1186 dependencies = [ 832 1187 "cfg-if", 1188 + "js-sys", 833 1189 "libc", 834 - "wasi 0.11.1+wasi-snapshot-preview1", 1190 + "wasi", 1191 + "wasm-bindgen", 835 1192 ] 836 1193 837 1194 [[package]] 838 1195 name = "getrandom" 839 - version = "0.3.3" 1196 + version = "0.3.4" 840 1197 source = "registry+https://github.com/rust-lang/crates.io-index" 841 - checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 1198 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 842 1199 dependencies = [ 843 1200 "cfg-if", 844 1201 "js-sys", 845 1202 "libc", 846 1203 "r-efi", 847 - "wasi 0.14.2+wasi-0.2.4", 1204 + "wasip2", 848 1205 "wasm-bindgen", 849 1206 ] 850 1207 ··· 869 1226 "aho-corasick", 870 1227 "bstr", 871 1228 "log", 872 - "regex-automata 0.4.9", 1229 + "regex-automata 0.4.13", 873 1230 "regex-syntax 0.8.5", 874 1231 ] 875 1232 ··· 884 1241 "futures-sink", 885 1242 "futures-timer", 886 1243 "futures-util", 887 - "getrandom 0.3.3", 1244 + "getrandom 0.3.4", 888 1245 "hashbrown 0.15.5", 889 1246 "nonzero_ext", 890 1247 "parking_lot", ··· 897 1254 ] 898 1255 899 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]] 900 1268 name = "h2" 901 1269 version = "0.4.12" 902 1270 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 908 1276 "futures-core", 909 1277 "futures-sink", 910 1278 "http", 911 - "indexmap", 1279 + "indexmap 2.10.0", 912 1280 "slab", 913 1281 "tokio", 914 1282 "tokio-util", ··· 944 1312 945 1313 [[package]] 946 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" 947 1321 version = "0.14.5" 948 1322 source = "registry+https://github.com/rust-lang/crates.io-index" 949 1323 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" ··· 974 1348 975 1349 [[package]] 976 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" 977 1357 version = "0.5.0" 978 1358 source = "registry+https://github.com/rust-lang/crates.io-index" 979 1359 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" ··· 985 1365 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 986 1366 987 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" 1372 + 1373 + [[package]] 988 1374 name = "hkdf" 989 1375 version = "0.12.4" 990 1376 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1009 1395 checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 1010 1396 dependencies = [ 1011 1397 "windows-sys 0.59.0", 1398 + ] 1399 + 1400 + [[package]] 1401 + name = "html-escape" 1402 + version = "0.2.13" 1403 + source = "registry+https://github.com/rust-lang/crates.io-index" 1404 + checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" 1405 + dependencies = [ 1406 + "utf8-width", 1012 1407 ] 1013 1408 1014 1409 [[package]] ··· 1079 1474 ] 1080 1475 1081 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]] 1082 1494 name = "hyper-timeout" 1083 1495 version = "0.5.2" 1084 1496 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1097 1509 source = "registry+https://github.com/rust-lang/crates.io-index" 1098 1510 checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 1099 1511 dependencies = [ 1512 + "base64", 1100 1513 "bytes", 1101 1514 "futures-channel", 1102 1515 "futures-core", ··· 1104 1517 "http", 1105 1518 "http-body", 1106 1519 "hyper", 1520 + "ipnet", 1107 1521 "libc", 1522 + "percent-encoding", 1108 1523 "pin-project-lite", 1109 1524 "socket2", 1525 + "system-configuration", 1110 1526 "tokio", 1111 1527 "tower-service", 1112 1528 "tracing", 1529 + "windows-registry", 1113 1530 ] 1114 1531 1115 1532 [[package]] ··· 1251 1668 1252 1669 [[package]] 1253 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" 1254 1682 version = "2.10.0" 1255 1683 source = "registry+https://github.com/rust-lang/crates.io-index" 1256 1684 checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 1257 1685 dependencies = [ 1258 1686 "equivalent", 1259 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", 1260 1698 ] 1261 1699 1262 1700 [[package]] ··· 1269 1707 ] 1270 1708 1271 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]] 1272 1719 name = "io-uring" 1273 1720 version = "0.7.9" 1274 1721 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1280 1727 ] 1281 1728 1282 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]] 1283 1757 name = "itertools" 1284 1758 version = "0.12.1" 1285 1759 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1295 1769 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1296 1770 1297 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]] 1298 1895 name = "jobserver" 1299 1896 version = "0.1.33" 1300 1897 source = "registry+https://github.com/rust-lang/crates.io-index" 1301 1898 checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1302 1899 dependencies = [ 1303 - "getrandom 0.3.3", 1900 + "getrandom 0.3.4", 1304 1901 "libc", 1305 1902 ] 1306 1903 1307 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]] 1308 1922 name = "js-sys" 1309 1923 version = "0.3.77" 1310 1924 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1337 1951 ] 1338 1952 1339 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]] 1340 1977 name = "lazy_static" 1341 1978 version = "1.5.0" 1342 1979 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1452 2089 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1453 2090 1454 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]] 1455 2109 name = "matchers" 1456 2110 version = "0.1.0" 1457 2111 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1483 2137 checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 1484 2138 1485 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]] 1486 2162 name = "mime" 1487 2163 version = "0.3.17" 1488 2164 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1501 2177 checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1502 2178 dependencies = [ 1503 2179 "adler2", 2180 + "simd-adler32", 1504 2181 ] 1505 2182 1506 2183 [[package]] ··· 1510 2187 checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 1511 2188 dependencies = [ 1512 2189 "libc", 1513 - "wasi 0.11.1+wasi-snapshot-preview1", 2190 + "wasi", 1514 2191 "windows-sys 0.59.0", 1515 2192 ] 1516 2193 1517 2194 [[package]] 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]] 1518 2218 name = "nom" 1519 2219 version = "7.1.3" 1520 2220 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1573 2273 ] 1574 2274 1575 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" 2280 + 2281 + [[package]] 1576 2282 name = "num-integer" 1577 2283 version = "0.1.46" 1578 2284 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1633 2339 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1634 2340 1635 2341 [[package]] 2342 + name = "openssl" 2343 + version = "0.10.75" 2344 + source = "registry+https://github.com/rust-lang/crates.io-index" 2345 + checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" 2346 + dependencies = [ 2347 + "bitflags", 2348 + "cfg-if", 2349 + "foreign-types", 2350 + "libc", 2351 + "once_cell", 2352 + "openssl-macros", 2353 + "openssl-sys", 2354 + ] 2355 + 2356 + [[package]] 2357 + name = "openssl-macros" 2358 + version = "0.1.1" 2359 + source = "registry+https://github.com/rust-lang/crates.io-index" 2360 + checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 2361 + dependencies = [ 2362 + "proc-macro2", 2363 + "quote", 2364 + "syn 2.0.105", 2365 + ] 2366 + 2367 + [[package]] 2368 + name = "openssl-sys" 2369 + version = "0.9.111" 2370 + source = "registry+https://github.com/rust-lang/crates.io-index" 2371 + checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" 2372 + dependencies = [ 2373 + "cc", 2374 + "libc", 2375 + "pkg-config", 2376 + "vcpkg", 2377 + ] 2378 + 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]] 1636 2404 name = "overload" 1637 2405 version = "0.1.1" 1638 2406 source = "registry+https://github.com/rust-lang/crates.io-index" 1639 2407 checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1640 2408 1641 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 + ] 2420 + 2421 + [[package]] 1642 2422 name = "parking" 1643 2423 version = "2.2.1" 1644 2424 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1700 2480 "dotenvy", 1701 2481 "handlebars", 1702 2482 "hex", 2483 + "html-escape", 1703 2484 "hyper-util", 2485 + "jacquard-common", 2486 + "jacquard-identity", 2487 + "josekit", 1704 2488 "jwt-compact", 1705 2489 "lettre", 2490 + "multibase", 1706 2491 "rand 0.9.2", 2492 + "reqwest", 1707 2493 "rust-embed", 1708 2494 "rustls", 1709 2495 "scrypt", ··· 1716 2502 "tower_governor", 1717 2503 "tracing", 1718 2504 "tracing-subscriber", 2505 + "urlencoding", 1719 2506 ] 1720 2507 1721 2508 [[package]] ··· 1764 2551 "pest_meta", 1765 2552 "proc-macro2", 1766 2553 "quote", 1767 - "syn", 2554 + "syn 2.0.105", 1768 2555 ] 1769 2556 1770 2557 [[package]] ··· 1794 2581 dependencies = [ 1795 2582 "proc-macro2", 1796 2583 "quote", 1797 - "syn", 2584 + "syn 2.0.105", 1798 2585 ] 1799 2586 1800 2587 [[package]] ··· 1852 2639 ] 1853 2640 1854 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]] 1855 2648 name = "ppv-lite86" 1856 2649 version = "0.2.21" 1857 2650 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1867 2660 checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" 1868 2661 dependencies = [ 1869 2662 "proc-macro2", 1870 - "syn", 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", 1871 2697 ] 1872 2698 1873 2699 [[package]] ··· 1880 2706 ] 1881 2707 1882 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]] 1883 2722 name = "psm" 1884 2723 version = "0.1.26" 1885 2724 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1898 2737 "libc", 1899 2738 "once_cell", 1900 2739 "raw-cpuid", 1901 - "wasi 0.11.1+wasi-snapshot-preview1", 2740 + "wasi", 1902 2741 "web-sys", 1903 2742 "winapi", 1904 2743 ] 1905 2744 1906 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]] 1907 2801 name = "quote" 1908 2802 version = "1.0.40" 1909 2803 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1980 2874 source = "registry+https://github.com/rust-lang/crates.io-index" 1981 2875 checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1982 2876 dependencies = [ 1983 - "getrandom 0.3.3", 2877 + "getrandom 0.3.4", 1984 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" 1985 2885 1986 2886 [[package]] 1987 2887 name = "raw-cpuid" ··· 2002 2902 ] 2003 2903 2004 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]] 2005 2925 name = "regex" 2006 - version = "1.11.1" 2926 + version = "1.12.2" 2007 2927 source = "registry+https://github.com/rust-lang/crates.io-index" 2008 - checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 2928 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 2009 2929 dependencies = [ 2010 2930 "aho-corasick", 2011 2931 "memchr", 2012 - "regex-automata 0.4.9", 2932 + "regex-automata 0.4.13", 2013 2933 "regex-syntax 0.8.5", 2014 2934 ] 2015 2935 ··· 2024 2944 2025 2945 [[package]] 2026 2946 name = "regex-automata" 2027 - version = "0.4.9" 2947 + version = "0.4.13" 2028 2948 source = "registry+https://github.com/rust-lang/crates.io-index" 2029 - checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 2949 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 2030 2950 dependencies = [ 2031 2951 "aho-corasick", 2032 2952 "memchr", ··· 2034 2954 ] 2035 2955 2036 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]] 2037 2963 name = "regex-syntax" 2038 2964 version = "0.6.29" 2039 2965 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2046 2972 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 2047 2973 2048 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]] 2049 3030 name = "ring" 2050 3031 version = "0.17.14" 2051 3032 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2099 3080 "proc-macro2", 2100 3081 "quote", 2101 3082 "rust-embed-utils", 2102 - "syn", 3083 + "syn 2.0.105", 2103 3084 "walkdir", 2104 3085 ] 2105 3086 ··· 2127 3108 checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 2128 3109 2129 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]] 2130 3117 name = "rustix" 2131 3118 version = "0.38.44" 2132 3119 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2161 3148 source = "registry+https://github.com/rust-lang/crates.io-index" 2162 3149 checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2163 3150 dependencies = [ 3151 + "web-time", 2164 3152 "zeroize", 2165 3153 ] 2166 3154 ··· 2207 3195 ] 2208 3196 2209 3197 [[package]] 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" 3212 + source = "registry+https://github.com/rust-lang/crates.io-index" 3213 + checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" 3214 + dependencies = [ 3215 + "dyn-clone", 3216 + "ref-cast", 3217 + "serde", 3218 + "serde_json", 3219 + ] 3220 + 3221 + [[package]] 2210 3222 name = "scopeguard" 2211 3223 version = "1.2.0" 2212 3224 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2222 3234 "pbkdf2", 2223 3235 "salsa20", 2224 3236 "sha2", 3237 + ] 3238 + 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", 2225 3251 ] 2226 3252 2227 3253 [[package]] ··· 2244 3270 2245 3271 [[package]] 2246 3272 name = "serde" 2247 - version = "1.0.219" 3273 + version = "1.0.228" 2248 3274 source = "registry+https://github.com/rust-lang/crates.io-index" 2249 - checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 3275 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 3276 + dependencies = [ 3277 + "serde_core", 3278 + "serde_derive", 3279 + ] 3280 + 3281 + [[package]] 3282 + name = "serde_bytes" 3283 + version = "0.11.19" 3284 + source = "registry+https://github.com/rust-lang/crates.io-index" 3285 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 3286 + dependencies = [ 3287 + "serde", 3288 + "serde_core", 3289 + ] 3290 + 3291 + [[package]] 3292 + name = "serde_core" 3293 + version = "1.0.228" 3294 + source = "registry+https://github.com/rust-lang/crates.io-index" 3295 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 2250 3296 dependencies = [ 2251 3297 "serde_derive", 2252 3298 ] 2253 3299 2254 3300 [[package]] 2255 3301 name = "serde_derive" 2256 - version = "1.0.219" 3302 + version = "1.0.228" 2257 3303 source = "registry+https://github.com/rust-lang/crates.io-index" 2258 - checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 3304 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 2259 3305 dependencies = [ 2260 3306 "proc-macro2", 2261 3307 "quote", 2262 - "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", 2263 3334 ] 2264 3335 2265 3336 [[package]] 2266 3337 name = "serde_json" 2267 - version = "1.0.142" 3338 + version = "1.0.145" 2268 3339 source = "registry+https://github.com/rust-lang/crates.io-index" 2269 - checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 3340 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 2270 3341 dependencies = [ 3342 + "indexmap 2.10.0", 2271 3343 "itoa", 2272 3344 "memchr", 2273 3345 "ryu", 2274 3346 "serde", 3347 + "serde_core", 2275 3348 ] 2276 3349 2277 3350 [[package]] ··· 2285 3358 ] 2286 3359 2287 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]] 2288 3372 name = "serde_urlencoded" 2289 3373 version = "0.7.1" 2290 3374 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2297 3381 ] 2298 3382 2299 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]] 2300 3415 name = "sha1" 2301 3416 version = "0.10.6" 2302 3417 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2353 3468 ] 2354 3469 2355 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]] 2356 3477 name = "slab" 2357 3478 version = "0.4.11" 2358 3479 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2368 3489 ] 2369 3490 2370 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]] 2371 3502 name = "socket2" 2372 3503 version = "0.6.0" 2373 3504 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2437 3568 "futures-util", 2438 3569 "hashbrown 0.15.5", 2439 3570 "hashlink", 2440 - "indexmap", 3571 + "indexmap 2.10.0", 2441 3572 "log", 2442 3573 "memchr", 2443 3574 "once_cell", ··· 2465 3596 "quote", 2466 3597 "sqlx-core", 2467 3598 "sqlx-macros-core", 2468 - "syn", 3599 + "syn 2.0.105", 2469 3600 ] 2470 3601 2471 3602 [[package]] ··· 2476 3607 dependencies = [ 2477 3608 "dotenvy", 2478 3609 "either", 2479 - "heck", 3610 + "heck 0.5.0", 2480 3611 "hex", 2481 3612 "once_cell", 2482 3613 "proc-macro2", ··· 2488 3619 "sqlx-mysql", 2489 3620 "sqlx-postgres", 2490 3621 "sqlx-sqlite", 2491 - "syn", 3622 + "syn 2.0.105", 2492 3623 "tokio", 2493 3624 "url", 2494 3625 ] ··· 2619 3750 ] 2620 3751 2621 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]] 2622 3779 name = "stringprep" 2623 3780 version = "0.1.5" 2624 3781 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2643 3800 2644 3801 [[package]] 2645 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" 2646 3814 version = "2.0.105" 2647 3815 source = "registry+https://github.com/rust-lang/crates.io-index" 2648 3816 checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" ··· 2657 3825 version = "1.0.2" 2658 3826 source = "registry+https://github.com/rust-lang/crates.io-index" 2659 3827 checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 3828 + dependencies = [ 3829 + "futures-core", 3830 + ] 2660 3831 2661 3832 [[package]] 2662 3833 name = "synstructure" ··· 2666 3837 dependencies = [ 2667 3838 "proc-macro2", 2668 3839 "quote", 2669 - "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", 3852 + ] 3853 + 3854 + [[package]] 3855 + name = "system-configuration-sys" 3856 + version = "0.6.0" 3857 + source = "registry+https://github.com/rust-lang/crates.io-index" 3858 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 3859 + dependencies = [ 3860 + "core-foundation-sys", 3861 + "libc", 2670 3862 ] 2671 3863 2672 3864 [[package]] ··· 2695 3887 dependencies = [ 2696 3888 "proc-macro2", 2697 3889 "quote", 2698 - "syn", 3890 + "syn 2.0.105", 2699 3891 ] 2700 3892 2701 3893 [[package]] ··· 2706 3898 dependencies = [ 2707 3899 "proc-macro2", 2708 3900 "quote", 2709 - "syn", 3901 + "syn 2.0.105", 2710 3902 ] 2711 3903 2712 3904 [[package]] ··· 2719 3911 ] 2720 3912 2721 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]] 2722 3945 name = "tinystr" 2723 3946 version = "0.8.1" 2724 3947 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2770 3993 dependencies = [ 2771 3994 "proc-macro2", 2772 3995 "quote", 2773 - "syn", 3996 + "syn 2.0.105", 2774 3997 ] 2775 3998 2776 3999 [[package]] ··· 2796 4019 2797 4020 [[package]] 2798 4021 name = "tokio-util" 2799 - version = "0.7.15" 4022 + version = "0.7.17" 2800 4023 source = "registry+https://github.com/rust-lang/crates.io-index" 2801 - checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 4024 + checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 2802 4025 dependencies = [ 2803 4026 "bytes", 2804 4027 "futures-core", ··· 2844 4067 dependencies = [ 2845 4068 "futures-core", 2846 4069 "futures-util", 2847 - "indexmap", 4070 + "indexmap 2.10.0", 2848 4071 "pin-project-lite", 2849 4072 "slab", 2850 4073 "sync_wrapper", ··· 2865 4088 "bitflags", 2866 4089 "bytes", 2867 4090 "futures-core", 4091 + "futures-util", 2868 4092 "http", 2869 4093 "http-body", 4094 + "iri-string", 2870 4095 "pin-project-lite", 2871 4096 "tokio", 2872 4097 "tokio-util", 4098 + "tower", 2873 4099 "tower-layer", 2874 4100 "tower-service", 2875 4101 ] ··· 2923 4149 dependencies = [ 2924 4150 "proc-macro2", 2925 4151 "quote", 2926 - "syn", 4152 + "syn 2.0.105", 2927 4153 ] 2928 4154 2929 4155 [[package]] ··· 2966 4192 ] 2967 4193 2968 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]] 2969 4206 name = "try-lock" 2970 4207 version = "0.2.5" 2971 4208 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3011 4248 checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 3012 4249 3013 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]] 3014 4269 name = "untrusted" 3015 4270 version = "0.7.1" 3016 4271 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3031 4286 "form_urlencoded", 3032 4287 "idna", 3033 4288 "percent-encoding", 4289 + "serde", 3034 4290 ] 3035 4291 3036 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]] 3037 4305 name = "utf8_iter" 3038 4306 version = "1.0.4" 3039 4307 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3083 4351 checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3084 4352 3085 4353 [[package]] 3086 - name = "wasi" 3087 - version = "0.14.2+wasi-0.2.4" 4354 + name = "wasip2" 4355 + version = "1.0.1+wasi-0.2.4" 3088 4356 source = "registry+https://github.com/rust-lang/crates.io-index" 3089 - checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 4357 + checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 3090 4358 dependencies = [ 3091 - "wit-bindgen-rt", 4359 + "wit-bindgen", 3092 4360 ] 3093 4361 3094 4362 [[package]] ··· 3119 4387 "log", 3120 4388 "proc-macro2", 3121 4389 "quote", 3122 - "syn", 4390 + "syn 2.0.105", 3123 4391 "wasm-bindgen-shared", 3124 4392 ] 3125 4393 3126 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]] 3127 4408 name = "wasm-bindgen-macro" 3128 4409 version = "0.2.100" 3129 4410 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3141 4422 dependencies = [ 3142 4423 "proc-macro2", 3143 4424 "quote", 3144 - "syn", 4425 + "syn 2.0.105", 3145 4426 "wasm-bindgen-backend", 3146 4427 "wasm-bindgen-shared", 3147 4428 ] ··· 3153 4434 checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 3154 4435 dependencies = [ 3155 4436 "unicode-ident", 4437 + ] 4438 + 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", 3156 4450 ] 3157 4451 3158 4452 [[package]] ··· 3254 4548 dependencies = [ 3255 4549 "windows-implement", 3256 4550 "windows-interface", 3257 - "windows-link", 4551 + "windows-link 0.1.3", 3258 4552 "windows-result", 3259 4553 "windows-strings", 3260 4554 ] ··· 3267 4561 dependencies = [ 3268 4562 "proc-macro2", 3269 4563 "quote", 3270 - "syn", 4564 + "syn 2.0.105", 3271 4565 ] 3272 4566 3273 4567 [[package]] ··· 3278 4572 dependencies = [ 3279 4573 "proc-macro2", 3280 4574 "quote", 3281 - "syn", 4575 + "syn 2.0.105", 3282 4576 ] 3283 4577 3284 4578 [[package]] ··· 3288 4582 checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3289 4583 3290 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]] 3291 4602 name = "windows-result" 3292 4603 version = "0.3.4" 3293 4604 source = "registry+https://github.com/rust-lang/crates.io-index" 3294 4605 checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3295 4606 dependencies = [ 3296 - "windows-link", 4607 + "windows-link 0.1.3", 3297 4608 ] 3298 4609 3299 4610 [[package]] ··· 3302 4613 source = "registry+https://github.com/rust-lang/crates.io-index" 3303 4614 checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3304 4615 dependencies = [ 3305 - "windows-link", 4616 + "windows-link 0.1.3", 3306 4617 ] 3307 4618 3308 4619 [[package]] ··· 3454 4765 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3455 4766 3456 4767 [[package]] 3457 - name = "wit-bindgen-rt" 3458 - version = "0.39.0" 4768 + name = "wit-bindgen" 4769 + version = "0.46.0" 3459 4770 source = "registry+https://github.com/rust-lang/crates.io-index" 3460 - checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 3461 - dependencies = [ 3462 - "bitflags", 3463 - ] 4771 + checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 3464 4772 3465 4773 [[package]] 3466 4774 name = "writeable" ··· 3469 4777 checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 3470 4778 3471 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]] 3472 4786 name = "yoke" 3473 4787 version = "0.8.0" 3474 4788 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3488 4802 dependencies = [ 3489 4803 "proc-macro2", 3490 4804 "quote", 3491 - "syn", 4805 + "syn 2.0.105", 3492 4806 "synstructure", 3493 4807 ] 3494 4808 ··· 3509 4823 dependencies = [ 3510 4824 "proc-macro2", 3511 4825 "quote", 3512 - "syn", 4826 + "syn 2.0.105", 3513 4827 ] 3514 4828 3515 4829 [[package]] ··· 3529 4843 dependencies = [ 3530 4844 "proc-macro2", 3531 4845 "quote", 3532 - "syn", 4846 + "syn 2.0.105", 3533 4847 "synstructure", 3534 4848 ] 3535 4849 ··· 3550 4864 dependencies = [ 3551 4865 "proc-macro2", 3552 4866 "quote", 3553 - "syn", 4867 + "syn 2.0.105", 3554 4868 ] 3555 4869 3556 4870 [[package]] ··· 3583 4897 dependencies = [ 3584 4898 "proc-macro2", 3585 4899 "quote", 3586 - "syn", 4900 + "syn 2.0.105", 3587 4901 ] 3588 4902 3589 4903 [[package]]
+8 -1
Cargo.toml
··· 28 28 axum-template = { version = "3.0.0", features = ["handlebars"] } 29 29 rand = "0.9.2" 30 30 anyhow = "1.0.99" 31 - chrono = "0.4.41" 31 + chrono = { version = "0.4.42", features = ["default", "serde"] } 32 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"
+103 -67
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 + 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 19 42 20 - Future feature? 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 ··· 49 76 - pds 50 77 ``` 51 78 52 - For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml). 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). 53 81 54 82 ```yml 55 83 gatekeeper: 56 - container_name: gatekeeper 57 - image: 'fatfingers23/pds_gatekeeper:latest' 58 - restart: unless-stopped 59 - volumes: 60 - - '/pds:/pds' 61 - environment: 62 - - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 63 - - 'PDS_BASE_URL=http://pds:3000' 64 - - GATEKEEPER_HOST=0.0.0.0 65 - depends_on: 66 - - pds 67 - healthcheck: 68 - test: 69 - - CMD 70 - - timeout 71 - - '1' 72 - - bash 73 - - '-c' 74 - - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080' 75 - interval: 10s 76 - timeout: 5s 77 - retries: 3 78 - start_period: 10s 79 - labels: 80 - - traefik.enable=true 81 - - '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`))' 82 - - traefik.http.routers.pds-gatekeeper.entrypoints=https 83 - - traefik.http.routers.pds-gatekeeper.tls=true 84 - - traefik.http.routers.pds-gatekeeper.priority=100 85 - - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors 86 - - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080 87 - - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http 88 - - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH' 89 - - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*' 90 - - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*' 91 - - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100 92 - - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true 93 - - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true 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 94 122 ``` 95 123 96 124 ## Caddy setup ··· 99 127 in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile). 100 128 This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS. 101 129 102 - ```caddyfile 130 + ``` 103 131 @gatekeeper { 104 - path /xrpc/com.atproto.server.getSession 105 - path /xrpc/com.atproto.server.updateEmail 106 - path /xrpc/com.atproto.server.createSession 107 - path /xrpc/com.atproto.server.createAccount 108 - path /@atproto/oauth-provider/~api/sign-in 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/* 109 139 } 110 140 111 141 handle @gatekeeper { 112 - reverse_proxy http://localhost:8080 113 - } 142 + reverse_proxy http://localhost:8080 143 + } 114 144 115 - reverse_proxy http://localhost:3000 145 + reverse_proxy http://localhost:3000 116 146 ``` 117 147 118 148 If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to 119 149 `localhost:8081` (or w/e port you want). 120 150 121 - ```caddyfile 151 + ``` 122 152 http://*.localhost:8082, http://localhost:8082 { 123 - @gatekeeper { 124 - path /xrpc/com.atproto.server.getSession 125 - path /xrpc/com.atproto.server.updateEmail 126 - path /xrpc/com.atproto.server.createSession 127 - path /xrpc/com.atproto.server.createAccount 128 - path /@atproto/oauth-provider/~api/sign-in 129 - } 153 + @gatekeeper { 154 + path /xrpc/com.atproto.server.getSession 155 + path /xrpc/com.atproto.server.describeServer 156 + path /xrpc/com.atproto.server.updateEmail 157 + path /xrpc/com.atproto.server.createSession 158 + path /xrpc/com.atproto.server.createAccount 159 + path /@atproto/oauth-provider/~api/sign-in 160 + path /gate/* 161 + } 130 162 131 - handle @gatekeeper { 132 - reverse_proxy http://localhost:8080 { 133 - #Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper 134 - header_up X-Forwarded-For {http.request.header.CF-Connecting-IP} 135 - } 136 - } 137 - 138 - reverse_proxy http://localhost:3000 163 + handle @gatekeeper { 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} 168 + } 169 + reverse_proxy http://localhost:3000 139 170 } 140 171 141 172 ``` ··· 168 199 169 200 `GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where 170 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 171 - off. 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 +
+22 -22
examples/Caddyfile
··· 1 1 { 2 - email youremail@myemail.com 3 - on_demand_tls { 4 - ask http://localhost:3000/tls-check 5 - } 2 + email youremail@myemail.com 3 + on_demand_tls { 4 + ask http://localhost:3000/tls-check 5 + } 6 6 } 7 7 8 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.updateEmail 16 - path /xrpc/com.atproto.server.createSession 17 - path /xrpc/com.atproto.server.createAccount 18 - path /@atproto/oauth-provider/~api/sign-in 9 + tls { 10 + on_demand 19 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 + } 20 22 21 - handle @gatekeeper { 22 - #This is the address for PDS gatekeeper, default is 8080 23 - reverse_proxy http://localhost:8080 24 - } 23 + handle @gatekeeper { 24 + #This is the address for PDS gatekeeper, default is 8080 25 + reverse_proxy http://localhost:8080 26 + } 25 27 26 - reverse_proxy http://localhost:3000 27 - #..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line 28 + reverse_proxy http://localhost:3000 29 + #..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line 28 30 } 29 - 30 -
+1 -1
examples/coolify-compose.yml
··· 58 58 start_period: 10s 59 59 labels: 60 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.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))' 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 62 - traefik.http.routers.pds-gatekeeper.entrypoints=https 63 63 - traefik.http.routers.pds-gatekeeper.tls=true 64 64 - traefik.http.routers.pds-gatekeeper.priority=100
+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>
+1 -1
justfile
··· 2 2 docker buildx build \ 3 3 --platform linux/arm64,linux/amd64 \ 4 4 --tag fatfingers23/pds_gatekeeper:latest \ 5 - --tag fatfingers23/pds_gatekeeper:0.1.0.3 \ 5 + --tag fatfingers23/pds_gatekeeper:0.1.0.5 \ 6 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 + }
+175 -13
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; 18 - use std::env; 28 + use std::sync::Arc; 19 29 use tracing::{error, log}; 20 30 21 31 ///Used to generate the email 2fa code ··· 40 50 where 41 51 T: DeserializeOwned, 42 52 { 43 - let uri = format!("{}{}", state.pds_base_url, path); 53 + let uri = format!("{}{}", state.app_config.pds_base_url, path); 44 54 *req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?; 45 55 46 56 let result = state ··· 333 343 let email_body = state 334 344 .template_engine 335 345 .render("two_factor_code.hbs", email_data)?; 336 - let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT") 337 - .unwrap_or("Sign in to Bluesky".to_string()); 338 346 339 347 let email_message = Message::builder() 340 348 //TODO prob get the proper type in the state 341 - .from(state.mailer_from.parse()?) 349 + .from(state.app_config.mailer_from.parse()?) 342 350 .to(email.parse()?) 343 - .subject(email_subject) 351 + .subject(&state.app_config.email_subject) 344 352 .multipart( 345 353 MultiPart::alternative() // This is composed of two parts. 346 354 .singlepart( ··· 523 531 524 532 format!("{masked_local}@{masked_domain}") 525 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 + }
+157 -29
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_account, 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_governor::key_extractor::SmartIpKeyExtractor; 24 - use tower_http::compression::CompressionLayer; 25 - 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 + }; 26 37 use tracing::log; 27 38 use tracing_subscriber::{EnvFilter, fmt, prelude::*}; 28 39 40 + mod gate; 29 41 pub mod helpers; 30 42 mod middleware; 31 43 mod oauth_provider; ··· 38 50 #[include = "*.hbs"] 39 51 struct EmailTemplates; 40 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 + 41 148 #[derive(Clone)] 42 149 pub struct AppState { 43 150 account_pool: SqlitePool, 44 151 pds_gatekeeper_pool: SqlitePool, 45 152 reverse_proxy_client: HyperUtilClient, 46 - pds_base_url: String, 47 153 mailer: AsyncSmtpTransport<Tokio1Executor>, 48 - mailer_from: String, 49 154 template_engine: Engine<Handlebars<'static>>, 155 + resolver: Arc<PublicResolver>, 156 + app_config: AppConfig, 50 157 } 51 158 52 159 async fn root_handler() -> impl axum::response::IntoResponse { ··· 137 244 //Emailer set up 138 245 let smtp_url = 139 246 env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file"); 140 - let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS") 141 - .expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file"); 142 247 143 248 let mailer: AsyncSmtpTransport<Tokio1Executor> = 144 249 AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build(); ··· 155 260 let _ = hbs.register_embed_templates::<EmailTemplates>(); 156 261 } 157 262 158 - let pds_base_url = 159 - 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()); 160 273 161 274 let state = AppState { 162 275 account_pool, 163 276 pds_gatekeeper_pool, 164 277 reverse_proxy_client: client, 165 - pds_base_url, 166 278 mailer, 167 - mailer_from: sent_from, 168 279 template_engine: Engine::from(hbs), 280 + resolver: Arc::new(resolver), 281 + app_config: AppConfig::new(), 169 282 }; 170 283 171 284 // Rate limiting 172 285 //Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds. 173 - let create_session_governor_conf = GovernorConfigBuilder::default() 286 + let captcha_governor_conf = GovernorConfigBuilder::default() 174 287 .per_second(60) 175 288 .burst_size(5) 176 289 .key_extractor(SmartIpKeyExtractor) ··· 216 329 "failed to create governor config for create account. this should not happen and is a bug", 217 330 ); 218 331 219 - let create_session_governor_limiter = create_session_governor_conf.limiter().clone(); 332 + let captcha_governor_limiter = captcha_governor_conf.limiter().clone(); 220 333 let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone(); 221 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); 222 337 223 338 let interval = Duration::from_secs(60); 224 339 // a separate background task to clean up 225 340 std::thread::spawn(move || { 226 341 loop { 227 342 std::thread::sleep(interval); 228 - create_session_governor_limiter.retain_recent(); 343 + captcha_governor_limiter.retain_recent(); 229 344 sign_in_governor_limiter.retain_recent(); 230 345 create_account_governor_limiter.retain_recent(); 231 346 } ··· 236 351 .allow_methods([Method::GET, Method::OPTIONS, Method::POST]) 237 352 .allow_headers(Any); 238 353 239 - let app = Router::new() 354 + let mut app = Router::new() 240 355 .route("/", get(root_handler)) 241 356 .route("/xrpc/com.atproto.server.getSession", get(get_session)) 242 357 .route( 358 + "/xrpc/com.atproto.server.describeServer", 359 + get(describe_server), 360 + ) 361 + .route( 243 362 "/xrpc/com.atproto.server.updateEmail", 244 363 post(update_email).layer(ax_middleware::from_fn(middleware::extract_did)), 245 364 ) 246 365 .route( 247 366 "/@atproto/oauth-provider/~api/sign-in", 248 - post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)), 367 + post(sign_in).layer(sign_in_governor_layer.clone()), 249 368 ) 250 369 .route( 251 370 "/xrpc/com.atproto.server.createSession", 252 - post(create_session.layer(GovernorLayer::new(create_session_governor_conf))), 371 + post(create_session.layer(sign_in_governor_layer)), 253 372 ) 254 373 .route( 255 374 "/xrpc/com.atproto.server.createAccount", 256 375 post(create_account).layer(GovernorLayer::new(create_account_governor_conf)), 257 - ) 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 258 386 .layer(CompressionLayer::new()) 259 387 .layer(cors) 260 388 .with_state(state);
+1 -1
src/oauth_provider.rs
··· 57 57 //No 2FA or already passed 58 58 let uri = format!( 59 59 "{}{}", 60 - state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in" 60 + state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in" 61 61 ); 62 62 63 63 let mut req = axum::http::Request::post(uri);
+306 -11
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, ··· 90 144 //No 2FA or already passed 91 145 let uri = format!( 92 146 "{}{}", 93 - state.pds_base_url, "/xrpc/com.atproto.server.createSession" 147 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession" 94 148 ); 95 149 96 150 let mut req = axum::http::Request::post(uri); ··· 230 284 // Updating the actual email address by sending it on to the PDS 231 285 let uri = format!( 232 286 "{}{}", 233 - state.pds_base_url, "/xrpc/com.atproto.server.updateEmail" 287 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail" 234 288 ); 235 289 let mut req = axum::http::Request::post(uri); 236 290 if let Some(req_headers) = req.headers_mut() { ··· 283 337 } 284 338 } 285 339 340 + pub async fn describe_server( 341 + State(state): State<AppState>, 342 + req: Request, 343 + ) -> Result<Response<Body>, StatusCode> { 344 + match proxy_get_json::<DescribeServerResponse>( 345 + &state, 346 + req, 347 + "/xrpc/com.atproto.server.describeServer", 348 + ) 349 + .await? 350 + { 351 + ProxiedResult::Parsed { 352 + value: mut server_info, 353 + .. 354 + } => { 355 + //This signifies the server is configured for captcha verification 356 + server_info.phone_verification_required = Some(state.app_config.use_captcha); 357 + Ok(Json(server_info).into_response()) 358 + } 359 + ProxiedResult::Passthrough(resp) => Ok(resp), 360 + } 361 + } 362 + 363 + /// Verify a gate code matches the handle and is not expired 364 + async fn verify_gate_code( 365 + state: &AppState, 366 + code: &str, 367 + handle: &str, 368 + ) -> Result<bool, anyhow::Error> { 369 + // First, decrypt and verify the JWE token 370 + let payload = match verify_gate_token(code, &state.app_config.gate_jwe_key) { 371 + Ok(p) => p, 372 + Err(e) => { 373 + log::warn!("Failed to decrypt gate token: {}", e); 374 + return Ok(false); 375 + } 376 + }; 377 + 378 + // Verify the handle matches 379 + if payload.handle != handle { 380 + log::warn!( 381 + "Gate code handle mismatch: expected {}, got {}", 382 + handle, 383 + payload.handle 384 + ); 385 + return Ok(false); 386 + } 387 + 388 + let created_at = chrono::DateTime::parse_from_rfc3339(&payload.created_at) 389 + .map_err(|e| anyhow::anyhow!("Failed to parse created_at from token: {}", e))? 390 + .with_timezone(&Utc); 391 + 392 + let now = Utc::now(); 393 + let age = now - created_at; 394 + 395 + // Check if the token is expired (5 minutes) 396 + if age > Duration::minutes(5) { 397 + log::warn!("Gate code expired for handle {}", handle); 398 + return Ok(false); 399 + } 400 + 401 + // Verify the token exists in the database (to prevent reuse) 402 + let row: Option<(String,)> = 403 + sqlx::query_as("SELECT code FROM gate_codes WHERE code = ? and handle = ? LIMIT 1") 404 + .bind(code) 405 + .bind(handle) 406 + .fetch_optional(&state.pds_gatekeeper_pool) 407 + .await?; 408 + 409 + if row.is_none() { 410 + log::warn!("Gate code not found in database or already used"); 411 + return Ok(false); 412 + } 413 + 414 + // Token is valid, delete it so it can't be reused 415 + //TODO probably also delete expired codes? Will need to do that at some point probably altho the where is on code and handle 416 + 417 + sqlx::query("DELETE FROM gate_codes WHERE code = ?") 418 + .bind(code) 419 + .execute(&state.pds_gatekeeper_pool) 420 + .await?; 421 + 422 + Ok(true) 423 + } 424 + 286 425 pub async fn create_account( 287 426 State(state): State<AppState>, 288 - mut req: Request, 427 + req: Request, 289 428 ) -> Result<Response<Body>, StatusCode> { 290 - //TODO if I add the block of only accounts authenticated just take the body as json here and grab the lxm token. No middle ware is needed 429 + let headers = req.headers().clone(); 430 + let body_bytes = to_bytes(req.into_body(), usize::MAX) 431 + .await 432 + .map_err(|_| StatusCode::BAD_REQUEST)?; 433 + 434 + // Parse the body to check for verification code 435 + let account_request: CreateAccountRequest = 436 + serde_json::from_slice(&body_bytes).map_err(|e| { 437 + log::error!("Failed to parse create account request: {}", e); 438 + StatusCode::BAD_REQUEST 439 + })?; 440 + 441 + // Check for service auth (migrations) if configured 442 + if state.app_config.allow_only_migrations { 443 + // Expect Authorization: Bearer <jwt> 444 + let auth_header = headers 445 + .get(header::AUTHORIZATION) 446 + .and_then(|v| v.to_str().ok()) 447 + .map(str::to_string); 448 + 449 + let Some(value) = auth_header else { 450 + log::error!("No Authorization header found in the request"); 451 + return json_error_response( 452 + StatusCode::UNAUTHORIZED, 453 + "InvalidAuth", 454 + "This PDS is configured to only allow accounts created by migrations via this endpoint.", 455 + ); 456 + }; 457 + 458 + // Ensure Bearer prefix 459 + let token = value.strip_prefix("Bearer ").unwrap_or("").trim(); 460 + if token.is_empty() { 461 + log::error!("No Service Auth token found in the Authorization header"); 462 + return json_error_response( 463 + StatusCode::UNAUTHORIZED, 464 + "InvalidAuth", 465 + "This PDS is configured to only allow accounts created by migrations via this endpoint.", 466 + ); 467 + } 468 + 469 + // Ensure a non-empty DID was provided when migrations are enabled 470 + let requested_did_str = match account_request.did.as_deref() { 471 + Some(s) if !s.trim().is_empty() => s, 472 + _ => { 473 + return json_error_response( 474 + StatusCode::BAD_REQUEST, 475 + "InvalidRequest", 476 + "The 'did' field is required when migrations are enforced.", 477 + ); 478 + } 479 + }; 480 + 481 + // Parse the DID into the expected type for verification 482 + let requested_did: JacquardDid<'static> = match requested_did_str.parse() { 483 + Ok(d) => d, 484 + Err(e) => { 485 + log::error!( 486 + "Invalid DID format provided in createAccount: {} | error: {}", 487 + requested_did_str, 488 + e 489 + ); 490 + return json_error_response( 491 + StatusCode::BAD_REQUEST, 492 + "InvalidRequest", 493 + "The 'did' field is not a valid DID.", 494 + ); 495 + } 496 + }; 291 497 498 + let nsid = "com.atproto.server.createAccount".parse().unwrap(); 499 + match verify_service_auth( 500 + token, 501 + &nsid, 502 + state.resolver.clone(), 503 + &state.app_config.pds_service_did, 504 + &requested_did, 505 + ) 506 + .await 507 + { 508 + //Just do nothing if it passes so it continues. 509 + Ok(_) => {} 510 + Err(err) => match err { 511 + VerifyServiceAuthError::AuthFailed => { 512 + return json_error_response( 513 + StatusCode::UNAUTHORIZED, 514 + "InvalidAuth", 515 + "This PDS is configured to only allow accounts created by migrations via this endpoint.", 516 + ); 517 + } 518 + VerifyServiceAuthError::Error(err) => { 519 + log::error!("Error verifying service auth token: {err}"); 520 + return json_error_response( 521 + StatusCode::BAD_REQUEST, 522 + "InvalidRequest", 523 + "There has been an error, please contact your PDS administrator for help and for them to review the server logs.", 524 + ); 525 + } 526 + }, 527 + } 528 + } 529 + 530 + // Check for captcha verification if configured 531 + if state.app_config.use_captcha { 532 + if let Some(ref verification_code) = account_request.verification_code { 533 + match verify_gate_code(&state, verification_code, &account_request.handle).await { 534 + //TODO has a few errors to support 535 + 536 + //expired token 537 + // { 538 + // "error": "ExpiredToken", 539 + // "message": "Token has expired" 540 + // } 541 + 542 + //TODO ALSO add rate limits on the /gate endpoints so they can't be abused 543 + Ok(true) => { 544 + log::info!("Gate code verified for handle: {}", account_request.handle); 545 + } 546 + Ok(false) => { 547 + log::warn!( 548 + "Invalid or expired gate code for handle: {}", 549 + account_request.handle 550 + ); 551 + return json_error_response( 552 + StatusCode::BAD_REQUEST, 553 + "InvalidToken", 554 + "Token could not be verified", 555 + ); 556 + } 557 + Err(e) => { 558 + log::error!("Error verifying gate code: {}", e); 559 + return json_error_response( 560 + StatusCode::INTERNAL_SERVER_ERROR, 561 + "InvalidToken", 562 + "Token could not be verified", 563 + ); 564 + } 565 + } 566 + } else { 567 + // No verification code provided but captcha is required 568 + log::warn!( 569 + "No verification code provided for account creation: {}", 570 + account_request.handle 571 + ); 572 + return json_error_response( 573 + StatusCode::BAD_REQUEST, 574 + "InvalidRequest", 575 + "Verification is now required on this server.", 576 + ); 577 + } 578 + } 579 + 580 + // Rebuild the request with the same body and headers 292 581 let uri = format!( 293 582 "{}{}", 294 - state.pds_base_url, "/xrpc/com.atproto.server.createAccount" 583 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.createAccount" 295 584 ); 296 585 297 - // Rewrite the URI to point at the upstream PDS; keep headers, method, and body intact 298 - *req.uri_mut() = uri.parse().map_err(|_| StatusCode::BAD_REQUEST)?; 586 + let mut new_req = axum::http::Request::post(&uri); 587 + if let Some(req_headers) = new_req.headers_mut() { 588 + *req_headers = headers; 589 + } 590 + 591 + let new_req = new_req 592 + .body(Body::from(body_bytes)) 593 + .map_err(|_| StatusCode::BAD_REQUEST)?; 299 594 300 595 let proxied = state 301 596 .reverse_proxy_client 302 - .request(req) 597 + .request(new_req) 303 598 .await 304 599 .map_err(|_| StatusCode::BAD_REQUEST)? 305 600 .into_response();