Microservice to bring 2FA to self hosted PDSes

work done on allow migrations only

gatewip

holy cow it works

working captcha

moved to some config

clean up aisle 10

better flag checking logic hopefully

hbs template

thinking the website works

matches the other captcha

I think it's all working?

rate limit

wrap up hopefully

readme clean up

+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();