Microservice to bring 2FA to self hosted PDSes

Compare changes

Choose any two refs to compare.

+4
.dockerignore
··· 1 + target 2 + /target 3 + **/.idea 4 + .idea
+1790 -161
Cargo.lock
··· 3 3 version = 4 4 4 5 5 [[package]] 6 + name = "abnf" 7 + version = "0.13.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a" 10 + dependencies = [ 11 + "abnf-core", 12 + "nom 7.1.3", 13 + ] 14 + 15 + [[package]] 16 + name = "abnf-core" 17 + version = "0.5.0" 18 + source = "registry+https://github.com/rust-lang/crates.io-index" 19 + checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d" 20 + dependencies = [ 21 + "nom 7.1.3", 22 + ] 23 + 24 + [[package]] 6 25 name = "addr2line" 7 26 version = "0.24.2" 8 27 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 39 58 ] 40 59 41 60 [[package]] 42 - name = "allocator-api2" 43 - version = "0.2.21" 61 + name = "aliasable" 62 + version = "0.1.3" 44 63 source = "registry+https://github.com/rust-lang/crates.io-index" 45 - checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 64 + checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" 46 65 47 66 [[package]] 48 - name = "android-tzdata" 49 - version = "0.1.1" 67 + name = "allocator-api2" 68 + version = "0.2.21" 50 69 source = "registry+https://github.com/rust-lang/crates.io-index" 51 - checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 70 + checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" 52 71 53 72 [[package]] 54 73 name = "android_system_properties" ··· 66 85 checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" 67 86 68 87 [[package]] 88 + name = "async-channel" 89 + version = "1.9.0" 90 + source = "registry+https://github.com/rust-lang/crates.io-index" 91 + checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" 92 + dependencies = [ 93 + "concurrent-queue", 94 + "event-listener 2.5.3", 95 + "futures-core", 96 + ] 97 + 98 + [[package]] 99 + name = "async-channel" 100 + version = "2.5.0" 101 + source = "registry+https://github.com/rust-lang/crates.io-index" 102 + checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" 103 + dependencies = [ 104 + "concurrent-queue", 105 + "event-listener-strategy", 106 + "futures-core", 107 + "pin-project-lite", 108 + ] 109 + 110 + [[package]] 69 111 name = "async-compression" 70 112 version = "0.4.27" 71 113 source = "registry+https://github.com/rust-lang/crates.io-index" 72 114 checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" 73 115 dependencies = [ 116 + "flate2", 74 117 "futures-core", 75 118 "memchr", 76 119 "pin-project-lite", ··· 80 123 ] 81 124 82 125 [[package]] 126 + name = "async-executor" 127 + version = "1.13.3" 128 + source = "registry+https://github.com/rust-lang/crates.io-index" 129 + checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" 130 + dependencies = [ 131 + "async-task", 132 + "concurrent-queue", 133 + "fastrand", 134 + "futures-lite", 135 + "pin-project-lite", 136 + "slab", 137 + ] 138 + 139 + [[package]] 140 + name = "async-global-executor" 141 + version = "2.4.1" 142 + source = "registry+https://github.com/rust-lang/crates.io-index" 143 + checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" 144 + dependencies = [ 145 + "async-channel 2.5.0", 146 + "async-executor", 147 + "async-io", 148 + "async-lock", 149 + "blocking", 150 + "futures-lite", 151 + "once_cell", 152 + ] 153 + 154 + [[package]] 155 + name = "async-io" 156 + version = "2.6.0" 157 + source = "registry+https://github.com/rust-lang/crates.io-index" 158 + checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" 159 + dependencies = [ 160 + "autocfg", 161 + "cfg-if", 162 + "concurrent-queue", 163 + "futures-io", 164 + "futures-lite", 165 + "parking", 166 + "polling", 167 + "rustix 1.1.2", 168 + "slab", 169 + "windows-sys 0.61.2", 170 + ] 171 + 172 + [[package]] 173 + name = "async-lock" 174 + version = "3.4.1" 175 + source = "registry+https://github.com/rust-lang/crates.io-index" 176 + checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" 177 + dependencies = [ 178 + "event-listener 5.4.1", 179 + "event-listener-strategy", 180 + "pin-project-lite", 181 + ] 182 + 183 + [[package]] 184 + name = "async-process" 185 + version = "2.5.0" 186 + source = "registry+https://github.com/rust-lang/crates.io-index" 187 + checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" 188 + dependencies = [ 189 + "async-channel 2.5.0", 190 + "async-io", 191 + "async-lock", 192 + "async-signal", 193 + "async-task", 194 + "blocking", 195 + "cfg-if", 196 + "event-listener 5.4.1", 197 + "futures-lite", 198 + "rustix 1.1.2", 199 + ] 200 + 201 + [[package]] 202 + name = "async-signal" 203 + version = "0.2.13" 204 + source = "registry+https://github.com/rust-lang/crates.io-index" 205 + checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" 206 + dependencies = [ 207 + "async-io", 208 + "async-lock", 209 + "atomic-waker", 210 + "cfg-if", 211 + "futures-core", 212 + "futures-io", 213 + "rustix 1.1.2", 214 + "signal-hook-registry", 215 + "slab", 216 + "windows-sys 0.61.2", 217 + ] 218 + 219 + [[package]] 220 + name = "async-std" 221 + version = "1.13.2" 222 + source = "registry+https://github.com/rust-lang/crates.io-index" 223 + checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" 224 + dependencies = [ 225 + "async-channel 1.9.0", 226 + "async-global-executor", 227 + "async-io", 228 + "async-lock", 229 + "async-process", 230 + "crossbeam-utils", 231 + "futures-channel", 232 + "futures-core", 233 + "futures-io", 234 + "futures-lite", 235 + "gloo-timers", 236 + "kv-log-macro", 237 + "log", 238 + "memchr", 239 + "once_cell", 240 + "pin-project-lite", 241 + "pin-utils", 242 + "slab", 243 + "wasm-bindgen-futures", 244 + ] 245 + 246 + [[package]] 247 + name = "async-task" 248 + version = "4.7.1" 249 + source = "registry+https://github.com/rust-lang/crates.io-index" 250 + checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" 251 + 252 + [[package]] 83 253 name = "async-trait" 84 254 version = "0.1.89" 85 255 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 87 257 dependencies = [ 88 258 "proc-macro2", 89 259 "quote", 90 - "syn", 260 + "syn 2.0.105", 91 261 ] 92 262 93 263 [[package]] ··· 112 282 checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 113 283 114 284 [[package]] 285 + name = "aws-lc-rs" 286 + version = "1.13.3" 287 + source = "registry+https://github.com/rust-lang/crates.io-index" 288 + checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" 289 + dependencies = [ 290 + "aws-lc-sys", 291 + "untrusted 0.7.1", 292 + "zeroize", 293 + ] 294 + 295 + [[package]] 296 + name = "aws-lc-sys" 297 + version = "0.30.0" 298 + source = "registry+https://github.com/rust-lang/crates.io-index" 299 + checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" 300 + dependencies = [ 301 + "bindgen", 302 + "cc", 303 + "cmake", 304 + "dunce", 305 + "fs_extra", 306 + ] 307 + 308 + [[package]] 115 309 name = "axum" 116 310 version = "0.8.4" 117 311 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 174 368 dependencies = [ 175 369 "proc-macro2", 176 370 "quote", 177 - "syn", 371 + "syn 2.0.105", 178 372 ] 179 373 180 374 [[package]] ··· 205 399 ] 206 400 207 401 [[package]] 402 + name = "base-x" 403 + version = "0.2.11" 404 + source = "registry+https://github.com/rust-lang/crates.io-index" 405 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 406 + 407 + [[package]] 408 + name = "base16ct" 409 + version = "0.2.0" 410 + source = "registry+https://github.com/rust-lang/crates.io-index" 411 + checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 412 + 413 + [[package]] 414 + name = "base256emoji" 415 + version = "1.0.2" 416 + source = "registry+https://github.com/rust-lang/crates.io-index" 417 + checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" 418 + dependencies = [ 419 + "const-str", 420 + "match-lookup", 421 + ] 422 + 423 + [[package]] 208 424 name = "base64" 209 425 version = "0.22.1" 210 426 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 217 433 checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" 218 434 219 435 [[package]] 436 + name = "bindgen" 437 + version = "0.69.5" 438 + source = "registry+https://github.com/rust-lang/crates.io-index" 439 + checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 440 + dependencies = [ 441 + "bitflags", 442 + "cexpr", 443 + "clang-sys", 444 + "itertools", 445 + "lazy_static", 446 + "lazycell", 447 + "log", 448 + "prettyplease", 449 + "proc-macro2", 450 + "quote", 451 + "regex", 452 + "rustc-hash 1.1.0", 453 + "shlex", 454 + "syn 2.0.105", 455 + "which", 456 + ] 457 + 458 + [[package]] 220 459 name = "bitflags" 221 460 version = "2.9.1" 222 461 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 235 474 ] 236 475 237 476 [[package]] 477 + name = "blocking" 478 + version = "1.6.2" 479 + source = "registry+https://github.com/rust-lang/crates.io-index" 480 + checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" 481 + dependencies = [ 482 + "async-channel 2.5.0", 483 + "async-task", 484 + "futures-io", 485 + "futures-lite", 486 + "piper", 487 + ] 488 + 489 + [[package]] 490 + name = "bon" 491 + version = "3.8.1" 492 + source = "registry+https://github.com/rust-lang/crates.io-index" 493 + checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1" 494 + dependencies = [ 495 + "bon-macros", 496 + "rustversion", 497 + ] 498 + 499 + [[package]] 500 + name = "bon-macros" 501 + version = "3.8.1" 502 + source = "registry+https://github.com/rust-lang/crates.io-index" 503 + checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645" 504 + dependencies = [ 505 + "darling 0.21.3", 506 + "ident_case", 507 + "prettyplease", 508 + "proc-macro2", 509 + "quote", 510 + "rustversion", 511 + "syn 2.0.105", 512 + ] 513 + 514 + [[package]] 515 + name = "borsh" 516 + version = "1.6.0" 517 + source = "registry+https://github.com/rust-lang/crates.io-index" 518 + checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" 519 + dependencies = [ 520 + "cfg_aliases", 521 + ] 522 + 523 + [[package]] 238 524 name = "bstr" 239 525 version = "1.12.0" 240 526 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 245 531 ] 246 532 247 533 [[package]] 534 + name = "btree-range-map" 535 + version = "0.7.2" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33" 538 + dependencies = [ 539 + "btree-slab", 540 + "cc-traits", 541 + "range-traits", 542 + "serde", 543 + "slab", 544 + ] 545 + 546 + [[package]] 547 + name = "btree-slab" 548 + version = "0.6.1" 549 + source = "registry+https://github.com/rust-lang/crates.io-index" 550 + checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c" 551 + dependencies = [ 552 + "cc-traits", 553 + "slab", 554 + "smallvec", 555 + ] 556 + 557 + [[package]] 248 558 name = "bumpalo" 249 559 version = "3.19.0" 250 560 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 261 571 version = "1.10.1" 262 572 source = "registry+https://github.com/rust-lang/crates.io-index" 263 573 checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 574 + dependencies = [ 575 + "serde", 576 + ] 577 + 578 + [[package]] 579 + name = "cbor4ii" 580 + version = "0.2.14" 581 + source = "registry+https://github.com/rust-lang/crates.io-index" 582 + checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4" 583 + dependencies = [ 584 + "serde", 585 + ] 264 586 265 587 [[package]] 266 588 name = "cc" ··· 274 596 ] 275 597 276 598 [[package]] 599 + name = "cc-traits" 600 + version = "2.0.0" 601 + source = "registry+https://github.com/rust-lang/crates.io-index" 602 + checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5" 603 + dependencies = [ 604 + "slab", 605 + ] 606 + 607 + [[package]] 608 + name = "cexpr" 609 + version = "0.6.0" 610 + source = "registry+https://github.com/rust-lang/crates.io-index" 611 + checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 612 + dependencies = [ 613 + "nom 7.1.3", 614 + ] 615 + 616 + [[package]] 277 617 name = "cfg-if" 278 618 version = "1.0.1" 279 619 source = "registry+https://github.com/rust-lang/crates.io-index" 280 620 checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" 281 621 282 622 [[package]] 623 + name = "cfg_aliases" 624 + version = "0.2.1" 625 + source = "registry+https://github.com/rust-lang/crates.io-index" 626 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 627 + 628 + [[package]] 283 629 name = "chrono" 284 - version = "0.4.41" 630 + version = "0.4.42" 285 631 source = "registry+https://github.com/rust-lang/crates.io-index" 286 - checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 632 + checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" 287 633 dependencies = [ 288 - "android-tzdata", 289 634 "iana-time-zone", 290 635 "js-sys", 291 636 "num-traits", 637 + "serde", 292 638 "wasm-bindgen", 293 - "windows-link", 639 + "windows-link 0.2.1", 294 640 ] 295 641 296 642 [[package]] ··· 331 677 ] 332 678 333 679 [[package]] 680 + name = "cid" 681 + version = "0.11.1" 682 + source = "registry+https://github.com/rust-lang/crates.io-index" 683 + checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a" 684 + dependencies = [ 685 + "core2", 686 + "multibase", 687 + "multihash", 688 + "serde", 689 + "serde_bytes", 690 + "unsigned-varint", 691 + ] 692 + 693 + [[package]] 334 694 name = "cipher" 335 695 version = "0.4.4" 336 696 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 341 701 ] 342 702 343 703 [[package]] 704 + name = "clang-sys" 705 + version = "1.8.1" 706 + source = "registry+https://github.com/rust-lang/crates.io-index" 707 + checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 708 + dependencies = [ 709 + "glob", 710 + "libc", 711 + "libloading", 712 + ] 713 + 714 + [[package]] 715 + name = "cmake" 716 + version = "0.1.54" 717 + source = "registry+https://github.com/rust-lang/crates.io-index" 718 + checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 719 + dependencies = [ 720 + "cc", 721 + ] 722 + 723 + [[package]] 344 724 name = "concurrent-queue" 345 725 version = "2.5.0" 346 726 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 356 736 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 357 737 358 738 [[package]] 739 + name = "const-str" 740 + version = "0.4.3" 741 + source = "registry+https://github.com/rust-lang/crates.io-index" 742 + checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" 743 + 744 + [[package]] 359 745 name = "core-foundation" 360 746 version = "0.9.4" 361 747 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 372 758 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 373 759 374 760 [[package]] 761 + name = "core2" 762 + version = "0.4.0" 763 + source = "registry+https://github.com/rust-lang/crates.io-index" 764 + checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" 765 + dependencies = [ 766 + "memchr", 767 + ] 768 + 769 + [[package]] 375 770 name = "cpufeatures" 376 771 version = "0.2.17" 377 772 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 396 791 checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 397 792 398 793 [[package]] 794 + name = "crc32fast" 795 + version = "1.5.0" 796 + source = "registry+https://github.com/rust-lang/crates.io-index" 797 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 798 + dependencies = [ 799 + "cfg-if", 800 + ] 801 + 802 + [[package]] 399 803 name = "crossbeam-queue" 400 804 version = "0.3.12" 401 805 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 417 821 checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" 418 822 419 823 [[package]] 824 + name = "crypto-bigint" 825 + version = "0.5.5" 826 + source = "registry+https://github.com/rust-lang/crates.io-index" 827 + checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" 828 + dependencies = [ 829 + "generic-array", 830 + "rand_core 0.6.4", 831 + "subtle", 832 + "zeroize", 833 + ] 834 + 835 + [[package]] 420 836 name = "crypto-common" 421 837 version = "0.1.6" 422 838 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 432 848 source = "registry+https://github.com/rust-lang/crates.io-index" 433 849 checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 434 850 dependencies = [ 435 - "darling_core", 436 - "darling_macro", 851 + "darling_core 0.20.11", 852 + "darling_macro 0.20.11", 853 + ] 854 + 855 + [[package]] 856 + name = "darling" 857 + version = "0.21.3" 858 + source = "registry+https://github.com/rust-lang/crates.io-index" 859 + checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 860 + dependencies = [ 861 + "darling_core 0.21.3", 862 + "darling_macro 0.21.3", 437 863 ] 438 864 439 865 [[package]] ··· 447 873 "proc-macro2", 448 874 "quote", 449 875 "strsim", 450 - "syn", 876 + "syn 2.0.105", 877 + ] 878 + 879 + [[package]] 880 + name = "darling_core" 881 + version = "0.21.3" 882 + source = "registry+https://github.com/rust-lang/crates.io-index" 883 + checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 884 + dependencies = [ 885 + "fnv", 886 + "ident_case", 887 + "proc-macro2", 888 + "quote", 889 + "strsim", 890 + "syn 2.0.105", 451 891 ] 452 892 453 893 [[package]] ··· 456 896 source = "registry+https://github.com/rust-lang/crates.io-index" 457 897 checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 458 898 dependencies = [ 459 - "darling_core", 899 + "darling_core 0.20.11", 460 900 "quote", 461 - "syn", 901 + "syn 2.0.105", 902 + ] 903 + 904 + [[package]] 905 + name = "darling_macro" 906 + version = "0.21.3" 907 + source = "registry+https://github.com/rust-lang/crates.io-index" 908 + checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 909 + dependencies = [ 910 + "darling_core 0.21.3", 911 + "quote", 912 + "syn 2.0.105", 462 913 ] 463 914 464 915 [[package]] ··· 476 927 ] 477 928 478 929 [[package]] 930 + name = "data-encoding" 931 + version = "2.9.0" 932 + source = "registry+https://github.com/rust-lang/crates.io-index" 933 + checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 934 + 935 + [[package]] 936 + name = "data-encoding-macro" 937 + version = "0.1.18" 938 + source = "registry+https://github.com/rust-lang/crates.io-index" 939 + checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" 940 + dependencies = [ 941 + "data-encoding", 942 + "data-encoding-macro-internal", 943 + ] 944 + 945 + [[package]] 946 + name = "data-encoding-macro-internal" 947 + version = "0.1.16" 948 + source = "registry+https://github.com/rust-lang/crates.io-index" 949 + checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" 950 + dependencies = [ 951 + "data-encoding", 952 + "syn 2.0.105", 953 + ] 954 + 955 + [[package]] 479 956 name = "der" 480 957 version = "0.7.10" 481 958 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 487 964 ] 488 965 489 966 [[package]] 967 + name = "deranged" 968 + version = "0.5.5" 969 + source = "registry+https://github.com/rust-lang/crates.io-index" 970 + checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" 971 + dependencies = [ 972 + "powerfmt", 973 + "serde_core", 974 + ] 975 + 976 + [[package]] 490 977 name = "derive_builder" 491 978 version = "0.20.2" 492 979 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 501 988 source = "registry+https://github.com/rust-lang/crates.io-index" 502 989 checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" 503 990 dependencies = [ 504 - "darling", 991 + "darling 0.20.11", 505 992 "proc-macro2", 506 993 "quote", 507 - "syn", 994 + "syn 2.0.105", 508 995 ] 509 996 510 997 [[package]] ··· 514 1001 checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" 515 1002 dependencies = [ 516 1003 "derive_builder_core", 517 - "syn", 1004 + "syn 2.0.105", 518 1005 ] 519 1006 520 1007 [[package]] ··· 537 1024 dependencies = [ 538 1025 "proc-macro2", 539 1026 "quote", 540 - "syn", 1027 + "syn 2.0.105", 541 1028 ] 542 1029 543 1030 [[package]] ··· 547 1034 checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 548 1035 549 1036 [[package]] 1037 + name = "dunce" 1038 + version = "1.0.5" 1039 + source = "registry+https://github.com/rust-lang/crates.io-index" 1040 + checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 1041 + 1042 + [[package]] 1043 + name = "dyn-clone" 1044 + version = "1.0.20" 1045 + source = "registry+https://github.com/rust-lang/crates.io-index" 1046 + checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" 1047 + 1048 + [[package]] 1049 + name = "ecdsa" 1050 + version = "0.16.9" 1051 + source = "registry+https://github.com/rust-lang/crates.io-index" 1052 + checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" 1053 + dependencies = [ 1054 + "der", 1055 + "digest", 1056 + "elliptic-curve", 1057 + "rfc6979", 1058 + "signature", 1059 + "spki", 1060 + ] 1061 + 1062 + [[package]] 550 1063 name = "either" 551 1064 version = "1.15.0" 552 1065 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 556 1069 ] 557 1070 558 1071 [[package]] 1072 + name = "elliptic-curve" 1073 + version = "0.13.8" 1074 + source = "registry+https://github.com/rust-lang/crates.io-index" 1075 + checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" 1076 + dependencies = [ 1077 + "base16ct", 1078 + "crypto-bigint", 1079 + "digest", 1080 + "ff", 1081 + "generic-array", 1082 + "group", 1083 + "pem-rfc7468", 1084 + "pkcs8", 1085 + "rand_core 0.6.4", 1086 + "sec1", 1087 + "subtle", 1088 + "zeroize", 1089 + ] 1090 + 1091 + [[package]] 559 1092 name = "email-encoding" 560 1093 version = "0.4.1" 561 1094 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 572 1105 checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" 573 1106 574 1107 [[package]] 1108 + name = "encoding_rs" 1109 + version = "0.8.35" 1110 + source = "registry+https://github.com/rust-lang/crates.io-index" 1111 + checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 1112 + dependencies = [ 1113 + "cfg-if", 1114 + ] 1115 + 1116 + [[package]] 575 1117 name = "equivalent" 576 1118 version = "1.0.2" 577 1119 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 600 1142 601 1143 [[package]] 602 1144 name = "event-listener" 1145 + version = "2.5.3" 1146 + source = "registry+https://github.com/rust-lang/crates.io-index" 1147 + checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" 1148 + 1149 + [[package]] 1150 + name = "event-listener" 603 1151 version = "5.4.1" 604 1152 source = "registry+https://github.com/rust-lang/crates.io-index" 605 1153 checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" ··· 610 1158 ] 611 1159 612 1160 [[package]] 1161 + name = "event-listener-strategy" 1162 + version = "0.5.4" 1163 + source = "registry+https://github.com/rust-lang/crates.io-index" 1164 + checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" 1165 + dependencies = [ 1166 + "event-listener 5.4.1", 1167 + "pin-project-lite", 1168 + ] 1169 + 1170 + [[package]] 613 1171 name = "fastrand" 614 1172 version = "2.3.0" 615 1173 source = "registry+https://github.com/rust-lang/crates.io-index" 616 1174 checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 617 1175 618 1176 [[package]] 1177 + name = "ff" 1178 + version = "0.13.1" 1179 + source = "registry+https://github.com/rust-lang/crates.io-index" 1180 + checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" 1181 + dependencies = [ 1182 + "rand_core 0.6.4", 1183 + "subtle", 1184 + ] 1185 + 1186 + [[package]] 1187 + name = "flate2" 1188 + version = "1.1.5" 1189 + source = "registry+https://github.com/rust-lang/crates.io-index" 1190 + checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" 1191 + dependencies = [ 1192 + "crc32fast", 1193 + "miniz_oxide", 1194 + ] 1195 + 1196 + [[package]] 619 1197 name = "flume" 620 1198 version = "0.11.1" 621 1199 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 655 1233 656 1234 [[package]] 657 1235 name = "form_urlencoded" 658 - version = "1.2.1" 1236 + version = "1.2.2" 659 1237 source = "registry+https://github.com/rust-lang/crates.io-index" 660 - checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 1238 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 661 1239 dependencies = [ 662 1240 "percent-encoding", 663 1241 ] ··· 673 1251 ] 674 1252 675 1253 [[package]] 1254 + name = "fs_extra" 1255 + version = "1.3.0" 1256 + source = "registry+https://github.com/rust-lang/crates.io-index" 1257 + checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 1258 + 1259 + [[package]] 676 1260 name = "futures-channel" 677 1261 version = "0.3.31" 678 1262 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 717 1301 checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 718 1302 719 1303 [[package]] 1304 + name = "futures-lite" 1305 + version = "2.6.1" 1306 + source = "registry+https://github.com/rust-lang/crates.io-index" 1307 + checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" 1308 + dependencies = [ 1309 + "fastrand", 1310 + "futures-core", 1311 + "futures-io", 1312 + "parking", 1313 + "pin-project-lite", 1314 + ] 1315 + 1316 + [[package]] 1317 + name = "futures-macro" 1318 + version = "0.3.31" 1319 + source = "registry+https://github.com/rust-lang/crates.io-index" 1320 + checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 1321 + dependencies = [ 1322 + "proc-macro2", 1323 + "quote", 1324 + "syn 2.0.105", 1325 + ] 1326 + 1327 + [[package]] 720 1328 name = "futures-sink" 721 1329 version = "0.3.31" 722 1330 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 742 1350 dependencies = [ 743 1351 "futures-core", 744 1352 "futures-io", 1353 + "futures-macro", 745 1354 "futures-sink", 746 1355 "futures-task", 747 1356 "memchr", ··· 758 1367 dependencies = [ 759 1368 "typenum", 760 1369 "version_check", 1370 + "zeroize", 761 1371 ] 762 1372 763 1373 [[package]] ··· 767 1377 checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 768 1378 dependencies = [ 769 1379 "cfg-if", 1380 + "js-sys", 770 1381 "libc", 771 - "wasi 0.11.1+wasi-snapshot-preview1", 1382 + "wasi", 1383 + "wasm-bindgen", 772 1384 ] 773 1385 774 1386 [[package]] 775 1387 name = "getrandom" 776 - version = "0.3.3" 1388 + version = "0.3.4" 777 1389 source = "registry+https://github.com/rust-lang/crates.io-index" 778 - checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 1390 + checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" 779 1391 dependencies = [ 780 1392 "cfg-if", 781 1393 "js-sys", 782 1394 "libc", 783 1395 "r-efi", 784 - "wasi 0.14.2+wasi-0.2.4", 1396 + "wasip2", 785 1397 "wasm-bindgen", 786 1398 ] 787 1399 ··· 792 1404 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 793 1405 794 1406 [[package]] 1407 + name = "glob" 1408 + version = "0.3.3" 1409 + source = "registry+https://github.com/rust-lang/crates.io-index" 1410 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1411 + 1412 + [[package]] 795 1413 name = "globset" 796 1414 version = "0.4.16" 797 1415 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 800 1418 "aho-corasick", 801 1419 "bstr", 802 1420 "log", 803 - "regex-automata 0.4.9", 1421 + "regex-automata 0.4.13", 804 1422 "regex-syntax 0.8.5", 805 1423 ] 806 1424 807 1425 [[package]] 1426 + name = "gloo-timers" 1427 + version = "0.3.0" 1428 + source = "registry+https://github.com/rust-lang/crates.io-index" 1429 + checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" 1430 + dependencies = [ 1431 + "futures-channel", 1432 + "futures-core", 1433 + "js-sys", 1434 + "wasm-bindgen", 1435 + ] 1436 + 1437 + [[package]] 808 1438 name = "governor" 809 1439 version = "0.10.1" 810 1440 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 815 1445 "futures-sink", 816 1446 "futures-timer", 817 1447 "futures-util", 818 - "getrandom 0.3.3", 1448 + "getrandom 0.3.4", 819 1449 "hashbrown 0.15.5", 820 1450 "nonzero_ext", 821 1451 "parking_lot", ··· 828 1458 ] 829 1459 830 1460 [[package]] 1461 + name = "group" 1462 + version = "0.13.0" 1463 + source = "registry+https://github.com/rust-lang/crates.io-index" 1464 + checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" 1465 + dependencies = [ 1466 + "ff", 1467 + "rand_core 0.6.4", 1468 + "subtle", 1469 + ] 1470 + 1471 + [[package]] 831 1472 name = "h2" 832 1473 version = "0.4.12" 833 1474 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 839 1480 "futures-core", 840 1481 "futures-sink", 841 1482 "http", 842 - "indexmap", 1483 + "indexmap 2.10.0", 843 1484 "slab", 844 1485 "tokio", 845 1486 "tokio-util", ··· 875 1516 876 1517 [[package]] 877 1518 name = "hashbrown" 1519 + version = "0.12.3" 1520 + source = "registry+https://github.com/rust-lang/crates.io-index" 1521 + checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 1522 + 1523 + [[package]] 1524 + name = "hashbrown" 878 1525 version = "0.14.5" 879 1526 source = "registry+https://github.com/rust-lang/crates.io-index" 880 1527 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" ··· 905 1552 906 1553 [[package]] 907 1554 name = "heck" 1555 + version = "0.4.1" 1556 + source = "registry+https://github.com/rust-lang/crates.io-index" 1557 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 1558 + 1559 + [[package]] 1560 + name = "heck" 908 1561 version = "0.5.0" 909 1562 source = "registry+https://github.com/rust-lang/crates.io-index" 910 1563 checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 911 1564 912 1565 [[package]] 1566 + name = "hermit-abi" 1567 + version = "0.5.2" 1568 + source = "registry+https://github.com/rust-lang/crates.io-index" 1569 + checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" 1570 + 1571 + [[package]] 913 1572 name = "hex" 914 1573 version = "0.4.3" 915 1574 source = "registry+https://github.com/rust-lang/crates.io-index" 916 1575 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 1576 + 1577 + [[package]] 1578 + name = "hex_fmt" 1579 + version = "0.3.0" 1580 + source = "registry+https://github.com/rust-lang/crates.io-index" 1581 + checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" 917 1582 918 1583 [[package]] 919 1584 name = "hkdf" ··· 943 1608 ] 944 1609 945 1610 [[package]] 946 - name = "hostname" 947 - version = "0.4.1" 1611 + name = "html-escape" 1612 + version = "0.2.13" 948 1613 source = "registry+https://github.com/rust-lang/crates.io-index" 949 - checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" 1614 + checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" 950 1615 dependencies = [ 951 - "cfg-if", 952 - "libc", 953 - "windows-link", 1616 + "utf8-width", 954 1617 ] 955 1618 956 1619 [[package]] ··· 1021 1684 ] 1022 1685 1023 1686 [[package]] 1687 + name = "hyper-rustls" 1688 + version = "0.27.7" 1689 + source = "registry+https://github.com/rust-lang/crates.io-index" 1690 + checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" 1691 + dependencies = [ 1692 + "http", 1693 + "hyper", 1694 + "hyper-util", 1695 + "rustls", 1696 + "rustls-pki-types", 1697 + "tokio", 1698 + "tokio-rustls", 1699 + "tower-service", 1700 + "webpki-roots 1.0.2", 1701 + ] 1702 + 1703 + [[package]] 1024 1704 name = "hyper-timeout" 1025 1705 version = "0.5.2" 1026 1706 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1039 1719 source = "registry+https://github.com/rust-lang/crates.io-index" 1040 1720 checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" 1041 1721 dependencies = [ 1722 + "base64", 1042 1723 "bytes", 1043 1724 "futures-channel", 1044 1725 "futures-core", ··· 1046 1727 "http", 1047 1728 "http-body", 1048 1729 "hyper", 1730 + "ipnet", 1049 1731 "libc", 1732 + "percent-encoding", 1050 1733 "pin-project-lite", 1051 1734 "socket2", 1735 + "system-configuration", 1052 1736 "tokio", 1053 1737 "tower-service", 1054 1738 "tracing", 1739 + "windows-registry", 1055 1740 ] 1056 1741 1057 1742 [[package]] ··· 1172 1857 1173 1858 [[package]] 1174 1859 name = "idna" 1175 - version = "1.0.3" 1860 + version = "1.1.0" 1176 1861 source = "registry+https://github.com/rust-lang/crates.io-index" 1177 - checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1862 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 1178 1863 dependencies = [ 1179 1864 "idna_adapter", 1180 1865 "smallvec", ··· 1193 1878 1194 1879 [[package]] 1195 1880 name = "indexmap" 1881 + version = "1.9.3" 1882 + source = "registry+https://github.com/rust-lang/crates.io-index" 1883 + checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 1884 + dependencies = [ 1885 + "autocfg", 1886 + "hashbrown 0.12.3", 1887 + "serde", 1888 + ] 1889 + 1890 + [[package]] 1891 + name = "indexmap" 1196 1892 version = "2.10.0" 1197 1893 source = "registry+https://github.com/rust-lang/crates.io-index" 1198 1894 checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" 1199 1895 dependencies = [ 1200 1896 "equivalent", 1201 1897 "hashbrown 0.15.5", 1898 + "serde", 1899 + ] 1900 + 1901 + [[package]] 1902 + name = "indoc" 1903 + version = "2.0.7" 1904 + source = "registry+https://github.com/rust-lang/crates.io-index" 1905 + checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" 1906 + dependencies = [ 1907 + "rustversion", 1202 1908 ] 1203 1909 1204 1910 [[package]] ··· 1211 1917 ] 1212 1918 1213 1919 [[package]] 1920 + name = "inventory" 1921 + version = "0.3.21" 1922 + source = "registry+https://github.com/rust-lang/crates.io-index" 1923 + checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" 1924 + dependencies = [ 1925 + "rustversion", 1926 + ] 1927 + 1928 + [[package]] 1214 1929 name = "io-uring" 1215 1930 version = "0.7.9" 1216 1931 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1222 1937 ] 1223 1938 1224 1939 [[package]] 1940 + name = "ipld-core" 1941 + version = "0.4.2" 1942 + source = "registry+https://github.com/rust-lang/crates.io-index" 1943 + checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db" 1944 + dependencies = [ 1945 + "cid", 1946 + "serde", 1947 + "serde_bytes", 1948 + ] 1949 + 1950 + [[package]] 1951 + name = "ipnet" 1952 + version = "2.11.0" 1953 + source = "registry+https://github.com/rust-lang/crates.io-index" 1954 + checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1955 + 1956 + [[package]] 1957 + name = "iri-string" 1958 + version = "0.7.9" 1959 + source = "registry+https://github.com/rust-lang/crates.io-index" 1960 + checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" 1961 + dependencies = [ 1962 + "memchr", 1963 + "serde", 1964 + ] 1965 + 1966 + [[package]] 1967 + name = "itertools" 1968 + version = "0.12.1" 1969 + source = "registry+https://github.com/rust-lang/crates.io-index" 1970 + checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1971 + dependencies = [ 1972 + "either", 1973 + ] 1974 + 1975 + [[package]] 1225 1976 name = "itoa" 1226 1977 version = "1.0.15" 1227 1978 source = "registry+https://github.com/rust-lang/crates.io-index" 1228 1979 checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1229 1980 1230 1981 [[package]] 1982 + name = "jacquard-api" 1983 + version = "0.9.2" 1984 + source = "registry+https://github.com/rust-lang/crates.io-index" 1985 + checksum = "bbbfd6e2b10fa1731f4d4e40c8f791956b0d4f804fb3efef891afec903f20597" 1986 + dependencies = [ 1987 + "bon", 1988 + "bytes", 1989 + "jacquard-common", 1990 + "jacquard-derive", 1991 + "jacquard-lexicon", 1992 + "miette", 1993 + "rustversion", 1994 + "serde", 1995 + "serde_ipld_dagcbor", 1996 + "thiserror 2.0.14", 1997 + "unicode-segmentation", 1998 + ] 1999 + 2000 + [[package]] 2001 + name = "jacquard-common" 2002 + version = "0.9.2" 2003 + source = "registry+https://github.com/rust-lang/crates.io-index" 2004 + checksum = "df86cb117d9f1c2b0251ba67c3f0e3f963fd22abc6cf8de0e02a7fc846c288ca" 2005 + dependencies = [ 2006 + "base64", 2007 + "bon", 2008 + "bytes", 2009 + "chrono", 2010 + "cid", 2011 + "getrandom 0.2.16", 2012 + "getrandom 0.3.4", 2013 + "http", 2014 + "ipld-core", 2015 + "k256", 2016 + "langtag", 2017 + "miette", 2018 + "multibase", 2019 + "multihash", 2020 + "ouroboros", 2021 + "p256", 2022 + "rand 0.9.2", 2023 + "regex", 2024 + "regex-lite", 2025 + "reqwest", 2026 + "serde", 2027 + "serde_html_form", 2028 + "serde_ipld_dagcbor", 2029 + "serde_json", 2030 + "signature", 2031 + "smol_str", 2032 + "thiserror 2.0.14", 2033 + "tokio", 2034 + "tokio-util", 2035 + "trait-variant", 2036 + "url", 2037 + ] 2038 + 2039 + [[package]] 2040 + name = "jacquard-derive" 2041 + version = "0.9.2" 2042 + source = "registry+https://github.com/rust-lang/crates.io-index" 2043 + checksum = "42ca61a69dc7aa8fb2d7163416514ff7df5d79f2e8b22e269f4610afa85572fe" 2044 + dependencies = [ 2045 + "heck 0.5.0", 2046 + "jacquard-lexicon", 2047 + "proc-macro2", 2048 + "quote", 2049 + "syn 2.0.105", 2050 + ] 2051 + 2052 + [[package]] 2053 + name = "jacquard-identity" 2054 + version = "0.9.2" 2055 + source = "registry+https://github.com/rust-lang/crates.io-index" 2056 + checksum = "1ef714cacebfca486558a9f8e205daf466bfba0466c4d0c450fd6d0252400a53" 2057 + dependencies = [ 2058 + "bon", 2059 + "bytes", 2060 + "http", 2061 + "jacquard-api", 2062 + "jacquard-common", 2063 + "jacquard-lexicon", 2064 + "miette", 2065 + "percent-encoding", 2066 + "reqwest", 2067 + "serde", 2068 + "serde_html_form", 2069 + "serde_json", 2070 + "thiserror 2.0.14", 2071 + "tokio", 2072 + "trait-variant", 2073 + "url", 2074 + "urlencoding", 2075 + ] 2076 + 2077 + [[package]] 2078 + name = "jacquard-lexicon" 2079 + version = "0.9.2" 2080 + source = "registry+https://github.com/rust-lang/crates.io-index" 2081 + checksum = "de87f2c938faea1b1f1b32d5b9e0c870e7b5bb5efbf96e3692ae2d8f6b2beb7a" 2082 + dependencies = [ 2083 + "cid", 2084 + "dashmap", 2085 + "heck 0.5.0", 2086 + "inventory", 2087 + "jacquard-common", 2088 + "miette", 2089 + "multihash", 2090 + "prettyplease", 2091 + "proc-macro2", 2092 + "quote", 2093 + "serde", 2094 + "serde_ipld_dagcbor", 2095 + "serde_json", 2096 + "serde_repr", 2097 + "serde_with", 2098 + "sha2", 2099 + "syn 2.0.105", 2100 + "thiserror 2.0.14", 2101 + "unicode-segmentation", 2102 + ] 2103 + 2104 + [[package]] 1231 2105 name = "jobserver" 1232 2106 version = "0.1.33" 1233 2107 source = "registry+https://github.com/rust-lang/crates.io-index" 1234 2108 checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" 1235 2109 dependencies = [ 1236 - "getrandom 0.3.3", 2110 + "getrandom 0.3.4", 1237 2111 "libc", 2112 + ] 2113 + 2114 + [[package]] 2115 + name = "josekit" 2116 + version = "0.10.3" 2117 + source = "registry+https://github.com/rust-lang/crates.io-index" 2118 + checksum = "a808e078330e6af222eb0044b71d4b1ff981bfef43e7bc8133a88234e0c86a0c" 2119 + dependencies = [ 2120 + "anyhow", 2121 + "base64", 2122 + "flate2", 2123 + "openssl", 2124 + "regex", 2125 + "serde", 2126 + "serde_json", 2127 + "thiserror 2.0.14", 2128 + "time", 1238 2129 ] 1239 2130 1240 2131 [[package]] ··· 1270 2161 ] 1271 2162 1272 2163 [[package]] 2164 + name = "k256" 2165 + version = "0.13.4" 2166 + source = "registry+https://github.com/rust-lang/crates.io-index" 2167 + checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" 2168 + dependencies = [ 2169 + "cfg-if", 2170 + "ecdsa", 2171 + "elliptic-curve", 2172 + "sha2", 2173 + ] 2174 + 2175 + [[package]] 2176 + name = "kv-log-macro" 2177 + version = "1.0.7" 2178 + source = "registry+https://github.com/rust-lang/crates.io-index" 2179 + checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" 2180 + dependencies = [ 2181 + "log", 2182 + ] 2183 + 2184 + [[package]] 2185 + name = "langtag" 2186 + version = "0.4.0" 2187 + source = "registry+https://github.com/rust-lang/crates.io-index" 2188 + checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600" 2189 + dependencies = [ 2190 + "serde", 2191 + "static-regular-grammar", 2192 + "thiserror 1.0.69", 2193 + ] 2194 + 2195 + [[package]] 1273 2196 name = "lazy_static" 1274 2197 version = "1.5.0" 1275 2198 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1277 2200 dependencies = [ 1278 2201 "spin", 1279 2202 ] 2203 + 2204 + [[package]] 2205 + name = "lazycell" 2206 + version = "1.3.0" 2207 + source = "registry+https://github.com/rust-lang/crates.io-index" 2208 + checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1280 2209 1281 2210 [[package]] 1282 2211 name = "lettre" ··· 1284 2213 source = "registry+https://github.com/rust-lang/crates.io-index" 1285 2214 checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56" 1286 2215 dependencies = [ 2216 + "async-std", 1287 2217 "async-trait", 1288 2218 "base64", 1289 2219 "chumsky", ··· 1292 2222 "fastrand", 1293 2223 "futures-io", 1294 2224 "futures-util", 1295 - "hostname", 1296 2225 "httpdate", 1297 2226 "idna", 1298 2227 "mime", 1299 - "native-tls", 1300 - "nom", 2228 + "nom 8.0.0", 1301 2229 "percent-encoding", 1302 2230 "quoted_printable", 2231 + "rustls", 1303 2232 "socket2", 1304 2233 "tokio", 1305 - "tokio-native-tls", 2234 + "tokio-rustls", 1306 2235 "url", 2236 + "webpki-roots 1.0.2", 1307 2237 ] 1308 2238 1309 2239 [[package]] ··· 1313 2243 checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" 1314 2244 1315 2245 [[package]] 2246 + name = "libloading" 2247 + version = "0.8.8" 2248 + source = "registry+https://github.com/rust-lang/crates.io-index" 2249 + checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" 2250 + dependencies = [ 2251 + "cfg-if", 2252 + "windows-targets 0.52.6", 2253 + ] 2254 + 2255 + [[package]] 1316 2256 name = "libm" 1317 2257 version = "0.2.15" 1318 2258 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1342 2282 1343 2283 [[package]] 1344 2284 name = "linux-raw-sys" 1345 - version = "0.9.4" 2285 + version = "0.4.15" 2286 + source = "registry+https://github.com/rust-lang/crates.io-index" 2287 + checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 2288 + 2289 + [[package]] 2290 + name = "linux-raw-sys" 2291 + version = "0.11.0" 1346 2292 source = "registry+https://github.com/rust-lang/crates.io-index" 1347 - checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 2293 + checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" 1348 2294 1349 2295 [[package]] 1350 2296 name = "litemap" ··· 1367 2313 version = "0.4.27" 1368 2314 source = "registry+https://github.com/rust-lang/crates.io-index" 1369 2315 checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 2316 + dependencies = [ 2317 + "value-bag", 2318 + ] 2319 + 2320 + [[package]] 2321 + name = "lru-slab" 2322 + version = "0.1.2" 2323 + source = "registry+https://github.com/rust-lang/crates.io-index" 2324 + checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" 2325 + 2326 + [[package]] 2327 + name = "match-lookup" 2328 + version = "0.1.1" 2329 + source = "registry+https://github.com/rust-lang/crates.io-index" 2330 + checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" 2331 + dependencies = [ 2332 + "proc-macro2", 2333 + "quote", 2334 + "syn 1.0.109", 2335 + ] 1370 2336 1371 2337 [[package]] 1372 2338 name = "matchers" ··· 1400 2366 checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" 1401 2367 1402 2368 [[package]] 2369 + name = "miette" 2370 + version = "7.6.0" 2371 + source = "registry+https://github.com/rust-lang/crates.io-index" 2372 + checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" 2373 + dependencies = [ 2374 + "cfg-if", 2375 + "miette-derive", 2376 + "unicode-width", 2377 + ] 2378 + 2379 + [[package]] 2380 + name = "miette-derive" 2381 + version = "7.6.0" 2382 + source = "registry+https://github.com/rust-lang/crates.io-index" 2383 + checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" 2384 + dependencies = [ 2385 + "proc-macro2", 2386 + "quote", 2387 + "syn 2.0.105", 2388 + ] 2389 + 2390 + [[package]] 1403 2391 name = "mime" 1404 2392 version = "0.3.17" 1405 2393 source = "registry+https://github.com/rust-lang/crates.io-index" 1406 2394 checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1407 2395 1408 2396 [[package]] 2397 + name = "minimal-lexical" 2398 + version = "0.2.1" 2399 + source = "registry+https://github.com/rust-lang/crates.io-index" 2400 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 2401 + 2402 + [[package]] 1409 2403 name = "miniz_oxide" 1410 2404 version = "0.8.9" 1411 2405 source = "registry+https://github.com/rust-lang/crates.io-index" 1412 2406 checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1413 2407 dependencies = [ 1414 2408 "adler2", 2409 + "simd-adler32", 1415 2410 ] 1416 2411 1417 2412 [[package]] ··· 1421 2416 checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 1422 2417 dependencies = [ 1423 2418 "libc", 1424 - "wasi 0.11.1+wasi-snapshot-preview1", 2419 + "wasi", 1425 2420 "windows-sys 0.59.0", 1426 2421 ] 1427 2422 1428 2423 [[package]] 1429 - name = "native-tls" 1430 - version = "0.2.14" 2424 + name = "multibase" 2425 + version = "0.9.2" 1431 2426 source = "registry+https://github.com/rust-lang/crates.io-index" 1432 - checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" 2427 + checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" 1433 2428 dependencies = [ 1434 - "libc", 1435 - "log", 1436 - "openssl", 1437 - "openssl-probe", 1438 - "openssl-sys", 1439 - "schannel", 1440 - "security-framework", 1441 - "security-framework-sys", 1442 - "tempfile", 2429 + "base-x", 2430 + "base256emoji", 2431 + "data-encoding", 2432 + "data-encoding-macro", 2433 + ] 2434 + 2435 + [[package]] 2436 + name = "multihash" 2437 + version = "0.19.3" 2438 + source = "registry+https://github.com/rust-lang/crates.io-index" 2439 + checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" 2440 + dependencies = [ 2441 + "core2", 2442 + "serde", 2443 + "unsigned-varint", 2444 + ] 2445 + 2446 + [[package]] 2447 + name = "nom" 2448 + version = "7.1.3" 2449 + source = "registry+https://github.com/rust-lang/crates.io-index" 2450 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 2451 + dependencies = [ 2452 + "memchr", 2453 + "minimal-lexical", 1443 2454 ] 1444 2455 1445 2456 [[package]] ··· 1491 2502 ] 1492 2503 1493 2504 [[package]] 2505 + name = "num-conv" 2506 + version = "0.1.0" 2507 + source = "registry+https://github.com/rust-lang/crates.io-index" 2508 + checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 2509 + 2510 + [[package]] 1494 2511 name = "num-integer" 1495 2512 version = "0.1.46" 1496 2513 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1552 2569 1553 2570 [[package]] 1554 2571 name = "openssl" 1555 - version = "0.10.73" 2572 + version = "0.10.75" 1556 2573 source = "registry+https://github.com/rust-lang/crates.io-index" 1557 - checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 2574 + checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" 1558 2575 dependencies = [ 1559 2576 "bitflags", 1560 2577 "cfg-if", ··· 1573 2590 dependencies = [ 1574 2591 "proc-macro2", 1575 2592 "quote", 1576 - "syn", 2593 + "syn 2.0.105", 1577 2594 ] 1578 2595 1579 2596 [[package]] 1580 - name = "openssl-probe" 1581 - version = "0.1.6" 1582 - source = "registry+https://github.com/rust-lang/crates.io-index" 1583 - checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 1584 - 1585 - [[package]] 1586 2597 name = "openssl-sys" 1587 - version = "0.9.109" 2598 + version = "0.9.111" 1588 2599 source = "registry+https://github.com/rust-lang/crates.io-index" 1589 - checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" 2600 + checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" 1590 2601 dependencies = [ 1591 2602 "cc", 1592 2603 "libc", ··· 1595 2606 ] 1596 2607 1597 2608 [[package]] 2609 + name = "ouroboros" 2610 + version = "0.18.5" 2611 + source = "registry+https://github.com/rust-lang/crates.io-index" 2612 + checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59" 2613 + dependencies = [ 2614 + "aliasable", 2615 + "ouroboros_macro", 2616 + "static_assertions", 2617 + ] 2618 + 2619 + [[package]] 2620 + name = "ouroboros_macro" 2621 + version = "0.18.5" 2622 + source = "registry+https://github.com/rust-lang/crates.io-index" 2623 + checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" 2624 + dependencies = [ 2625 + "heck 0.4.1", 2626 + "proc-macro2", 2627 + "proc-macro2-diagnostics", 2628 + "quote", 2629 + "syn 2.0.105", 2630 + ] 2631 + 2632 + [[package]] 1598 2633 name = "overload" 1599 2634 version = "0.1.1" 1600 2635 source = "registry+https://github.com/rust-lang/crates.io-index" 1601 2636 checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 2637 + 2638 + [[package]] 2639 + name = "p256" 2640 + version = "0.13.2" 2641 + source = "registry+https://github.com/rust-lang/crates.io-index" 2642 + checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" 2643 + dependencies = [ 2644 + "ecdsa", 2645 + "elliptic-curve", 2646 + "primeorder", 2647 + "sha2", 2648 + ] 1602 2649 1603 2650 [[package]] 1604 2651 name = "parking" ··· 1652 2699 1653 2700 [[package]] 1654 2701 name = "pds_gatekeeper" 1655 - version = "0.1.0" 2702 + version = "0.1.2" 1656 2703 dependencies = [ 1657 2704 "anyhow", 2705 + "aws-lc-rs", 1658 2706 "axum", 1659 2707 "axum-template", 1660 2708 "chrono", 1661 2709 "dotenvy", 1662 2710 "handlebars", 1663 2711 "hex", 2712 + "html-escape", 1664 2713 "hyper-util", 2714 + "jacquard-common", 2715 + "jacquard-identity", 2716 + "josekit", 1665 2717 "jwt-compact", 1666 2718 "lettre", 2719 + "multibase", 1667 2720 "rand 0.9.2", 2721 + "reqwest", 1668 2722 "rust-embed", 2723 + "rustls", 1669 2724 "scrypt", 1670 2725 "serde", 1671 2726 "serde_json", ··· 1676 2731 "tower_governor", 1677 2732 "tracing", 1678 2733 "tracing-subscriber", 2734 + "url", 2735 + "urlencoding", 1679 2736 ] 1680 2737 1681 2738 [[package]] ··· 1689 2746 1690 2747 [[package]] 1691 2748 name = "percent-encoding" 1692 - version = "2.3.1" 2749 + version = "2.3.2" 1693 2750 source = "registry+https://github.com/rust-lang/crates.io-index" 1694 - checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 2751 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1695 2752 1696 2753 [[package]] 1697 2754 name = "pest" ··· 1724 2781 "pest_meta", 1725 2782 "proc-macro2", 1726 2783 "quote", 1727 - "syn", 2784 + "syn 2.0.105", 1728 2785 ] 1729 2786 1730 2787 [[package]] ··· 1754 2811 dependencies = [ 1755 2812 "proc-macro2", 1756 2813 "quote", 1757 - "syn", 2814 + "syn 2.0.105", 1758 2815 ] 1759 2816 1760 2817 [[package]] ··· 1770 2827 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1771 2828 1772 2829 [[package]] 2830 + name = "piper" 2831 + version = "0.2.4" 2832 + source = "registry+https://github.com/rust-lang/crates.io-index" 2833 + checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" 2834 + dependencies = [ 2835 + "atomic-waker", 2836 + "fastrand", 2837 + "futures-io", 2838 + ] 2839 + 2840 + [[package]] 1773 2841 name = "pkcs1" 1774 2842 version = "0.7.5" 1775 2843 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1797 2865 checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" 1798 2866 1799 2867 [[package]] 2868 + name = "polling" 2869 + version = "3.11.0" 2870 + source = "registry+https://github.com/rust-lang/crates.io-index" 2871 + checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" 2872 + dependencies = [ 2873 + "cfg-if", 2874 + "concurrent-queue", 2875 + "hermit-abi", 2876 + "pin-project-lite", 2877 + "rustix 1.1.2", 2878 + "windows-sys 0.61.2", 2879 + ] 2880 + 2881 + [[package]] 1800 2882 name = "portable-atomic" 1801 2883 version = "1.11.1" 1802 2884 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1810 2892 dependencies = [ 1811 2893 "zerovec", 1812 2894 ] 2895 + 2896 + [[package]] 2897 + name = "powerfmt" 2898 + version = "0.2.0" 2899 + source = "registry+https://github.com/rust-lang/crates.io-index" 2900 + checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1813 2901 1814 2902 [[package]] 1815 2903 name = "ppv-lite86" ··· 1821 2909 ] 1822 2910 1823 2911 [[package]] 2912 + name = "prettyplease" 2913 + version = "0.2.35" 2914 + source = "registry+https://github.com/rust-lang/crates.io-index" 2915 + checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" 2916 + dependencies = [ 2917 + "proc-macro2", 2918 + "syn 2.0.105", 2919 + ] 2920 + 2921 + [[package]] 2922 + name = "primeorder" 2923 + version = "0.13.6" 2924 + source = "registry+https://github.com/rust-lang/crates.io-index" 2925 + checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" 2926 + dependencies = [ 2927 + "elliptic-curve", 2928 + ] 2929 + 2930 + [[package]] 2931 + name = "proc-macro-error" 2932 + version = "1.0.4" 2933 + source = "registry+https://github.com/rust-lang/crates.io-index" 2934 + checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 2935 + dependencies = [ 2936 + "proc-macro-error-attr", 2937 + "proc-macro2", 2938 + "quote", 2939 + "syn 1.0.109", 2940 + "version_check", 2941 + ] 2942 + 2943 + [[package]] 2944 + name = "proc-macro-error-attr" 2945 + version = "1.0.4" 2946 + source = "registry+https://github.com/rust-lang/crates.io-index" 2947 + checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 2948 + dependencies = [ 2949 + "proc-macro2", 2950 + "quote", 2951 + "version_check", 2952 + ] 2953 + 2954 + [[package]] 1824 2955 name = "proc-macro2" 1825 2956 version = "1.0.97" 1826 2957 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1830 2961 ] 1831 2962 1832 2963 [[package]] 2964 + name = "proc-macro2-diagnostics" 2965 + version = "0.10.1" 2966 + source = "registry+https://github.com/rust-lang/crates.io-index" 2967 + checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" 2968 + dependencies = [ 2969 + "proc-macro2", 2970 + "quote", 2971 + "syn 2.0.105", 2972 + "version_check", 2973 + "yansi", 2974 + ] 2975 + 2976 + [[package]] 1833 2977 name = "psm" 1834 2978 version = "0.1.26" 1835 2979 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1848 2992 "libc", 1849 2993 "once_cell", 1850 2994 "raw-cpuid", 1851 - "wasi 0.11.1+wasi-snapshot-preview1", 2995 + "wasi", 1852 2996 "web-sys", 1853 2997 "winapi", 1854 2998 ] 1855 2999 1856 3000 [[package]] 3001 + name = "quinn" 3002 + version = "0.11.9" 3003 + source = "registry+https://github.com/rust-lang/crates.io-index" 3004 + checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" 3005 + dependencies = [ 3006 + "bytes", 3007 + "cfg_aliases", 3008 + "pin-project-lite", 3009 + "quinn-proto", 3010 + "quinn-udp", 3011 + "rustc-hash 2.1.1", 3012 + "rustls", 3013 + "socket2", 3014 + "thiserror 2.0.14", 3015 + "tokio", 3016 + "tracing", 3017 + "web-time", 3018 + ] 3019 + 3020 + [[package]] 3021 + name = "quinn-proto" 3022 + version = "0.11.13" 3023 + source = "registry+https://github.com/rust-lang/crates.io-index" 3024 + checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" 3025 + dependencies = [ 3026 + "bytes", 3027 + "getrandom 0.3.4", 3028 + "lru-slab", 3029 + "rand 0.9.2", 3030 + "ring", 3031 + "rustc-hash 2.1.1", 3032 + "rustls", 3033 + "rustls-pki-types", 3034 + "slab", 3035 + "thiserror 2.0.14", 3036 + "tinyvec", 3037 + "tracing", 3038 + "web-time", 3039 + ] 3040 + 3041 + [[package]] 3042 + name = "quinn-udp" 3043 + version = "0.5.14" 3044 + source = "registry+https://github.com/rust-lang/crates.io-index" 3045 + checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" 3046 + dependencies = [ 3047 + "cfg_aliases", 3048 + "libc", 3049 + "once_cell", 3050 + "socket2", 3051 + "tracing", 3052 + "windows-sys 0.59.0", 3053 + ] 3054 + 3055 + [[package]] 1857 3056 name = "quote" 1858 3057 version = "1.0.40" 1859 3058 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1930 3129 source = "registry+https://github.com/rust-lang/crates.io-index" 1931 3130 checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" 1932 3131 dependencies = [ 1933 - "getrandom 0.3.3", 3132 + "getrandom 0.3.4", 1934 3133 ] 3134 + 3135 + [[package]] 3136 + name = "range-traits" 3137 + version = "0.3.2" 3138 + source = "registry+https://github.com/rust-lang/crates.io-index" 3139 + checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab" 1935 3140 1936 3141 [[package]] 1937 3142 name = "raw-cpuid" ··· 1952 3157 ] 1953 3158 1954 3159 [[package]] 3160 + name = "ref-cast" 3161 + version = "1.0.25" 3162 + source = "registry+https://github.com/rust-lang/crates.io-index" 3163 + checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" 3164 + dependencies = [ 3165 + "ref-cast-impl", 3166 + ] 3167 + 3168 + [[package]] 3169 + name = "ref-cast-impl" 3170 + version = "1.0.25" 3171 + source = "registry+https://github.com/rust-lang/crates.io-index" 3172 + checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" 3173 + dependencies = [ 3174 + "proc-macro2", 3175 + "quote", 3176 + "syn 2.0.105", 3177 + ] 3178 + 3179 + [[package]] 1955 3180 name = "regex" 1956 - version = "1.11.1" 3181 + version = "1.12.2" 1957 3182 source = "registry+https://github.com/rust-lang/crates.io-index" 1958 - checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 3183 + checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" 1959 3184 dependencies = [ 1960 3185 "aho-corasick", 1961 3186 "memchr", 1962 - "regex-automata 0.4.9", 3187 + "regex-automata 0.4.13", 1963 3188 "regex-syntax 0.8.5", 1964 3189 ] 1965 3190 ··· 1974 3199 1975 3200 [[package]] 1976 3201 name = "regex-automata" 1977 - version = "0.4.9" 3202 + version = "0.4.13" 1978 3203 source = "registry+https://github.com/rust-lang/crates.io-index" 1979 - checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 3204 + checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" 1980 3205 dependencies = [ 1981 3206 "aho-corasick", 1982 3207 "memchr", ··· 1984 3209 ] 1985 3210 1986 3211 [[package]] 3212 + name = "regex-lite" 3213 + version = "0.1.8" 3214 + source = "registry+https://github.com/rust-lang/crates.io-index" 3215 + checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 3216 + 3217 + [[package]] 1987 3218 name = "regex-syntax" 1988 3219 version = "0.6.29" 1989 3220 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1996 3227 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1997 3228 1998 3229 [[package]] 3230 + name = "reqwest" 3231 + version = "0.12.24" 3232 + source = "registry+https://github.com/rust-lang/crates.io-index" 3233 + checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" 3234 + dependencies = [ 3235 + "async-compression", 3236 + "base64", 3237 + "bytes", 3238 + "encoding_rs", 3239 + "futures-core", 3240 + "futures-util", 3241 + "h2", 3242 + "http", 3243 + "http-body", 3244 + "http-body-util", 3245 + "hyper", 3246 + "hyper-rustls", 3247 + "hyper-util", 3248 + "js-sys", 3249 + "log", 3250 + "mime", 3251 + "percent-encoding", 3252 + "pin-project-lite", 3253 + "quinn", 3254 + "rustls", 3255 + "rustls-pki-types", 3256 + "serde", 3257 + "serde_json", 3258 + "serde_urlencoded", 3259 + "sync_wrapper", 3260 + "tokio", 3261 + "tokio-rustls", 3262 + "tokio-util", 3263 + "tower", 3264 + "tower-http", 3265 + "tower-service", 3266 + "url", 3267 + "wasm-bindgen", 3268 + "wasm-bindgen-futures", 3269 + "wasm-streams", 3270 + "web-sys", 3271 + "webpki-roots 1.0.2", 3272 + ] 3273 + 3274 + [[package]] 3275 + name = "rfc6979" 3276 + version = "0.4.0" 3277 + source = "registry+https://github.com/rust-lang/crates.io-index" 3278 + checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" 3279 + dependencies = [ 3280 + "hmac", 3281 + "subtle", 3282 + ] 3283 + 3284 + [[package]] 1999 3285 name = "ring" 2000 3286 version = "0.17.14" 2001 3287 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2005 3291 "cfg-if", 2006 3292 "getrandom 0.2.16", 2007 3293 "libc", 2008 - "untrusted", 3294 + "untrusted 0.9.0", 2009 3295 "windows-sys 0.52.0", 2010 3296 ] 2011 3297 ··· 2049 3335 "proc-macro2", 2050 3336 "quote", 2051 3337 "rust-embed-utils", 2052 - "syn", 3338 + "syn 2.0.105", 2053 3339 "walkdir", 2054 3340 ] 2055 3341 ··· 2069 3355 version = "0.1.26" 2070 3356 source = "registry+https://github.com/rust-lang/crates.io-index" 2071 3357 checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" 3358 + 3359 + [[package]] 3360 + name = "rustc-hash" 3361 + version = "1.1.0" 3362 + source = "registry+https://github.com/rust-lang/crates.io-index" 3363 + checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 3364 + 3365 + [[package]] 3366 + name = "rustc-hash" 3367 + version = "2.1.1" 3368 + source = "registry+https://github.com/rust-lang/crates.io-index" 3369 + checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 2072 3370 2073 3371 [[package]] 2074 3372 name = "rustix" 2075 - version = "1.0.8" 3373 + version = "0.38.44" 2076 3374 source = "registry+https://github.com/rust-lang/crates.io-index" 2077 - checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" 3375 + checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 3376 + dependencies = [ 3377 + "bitflags", 3378 + "errno", 3379 + "libc", 3380 + "linux-raw-sys 0.4.15", 3381 + "windows-sys 0.59.0", 3382 + ] 3383 + 3384 + [[package]] 3385 + name = "rustix" 3386 + version = "1.1.2" 3387 + source = "registry+https://github.com/rust-lang/crates.io-index" 3388 + checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" 2078 3389 dependencies = [ 2079 3390 "bitflags", 2080 3391 "errno", 2081 3392 "libc", 2082 - "linux-raw-sys", 3393 + "linux-raw-sys 0.11.0", 2083 3394 "windows-sys 0.59.0", 2084 3395 ] 2085 3396 ··· 2089 3400 source = "registry+https://github.com/rust-lang/crates.io-index" 2090 3401 checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" 2091 3402 dependencies = [ 3403 + "aws-lc-rs", 3404 + "log", 2092 3405 "once_cell", 2093 3406 "ring", 2094 3407 "rustls-pki-types", ··· 2103 3416 source = "registry+https://github.com/rust-lang/crates.io-index" 2104 3417 checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 2105 3418 dependencies = [ 3419 + "web-time", 2106 3420 "zeroize", 2107 3421 ] 2108 3422 ··· 2112 3426 source = "registry+https://github.com/rust-lang/crates.io-index" 2113 3427 checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" 2114 3428 dependencies = [ 3429 + "aws-lc-rs", 2115 3430 "ring", 2116 3431 "rustls-pki-types", 2117 - "untrusted", 3432 + "untrusted 0.9.0", 2118 3433 ] 2119 3434 2120 3435 [[package]] ··· 2148 3463 ] 2149 3464 2150 3465 [[package]] 2151 - name = "schannel" 2152 - version = "0.1.27" 3466 + name = "schemars" 3467 + version = "0.9.0" 3468 + source = "registry+https://github.com/rust-lang/crates.io-index" 3469 + checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" 3470 + dependencies = [ 3471 + "dyn-clone", 3472 + "ref-cast", 3473 + "serde", 3474 + "serde_json", 3475 + ] 3476 + 3477 + [[package]] 3478 + name = "schemars" 3479 + version = "1.1.0" 2153 3480 source = "registry+https://github.com/rust-lang/crates.io-index" 2154 - checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 3481 + checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" 2155 3482 dependencies = [ 2156 - "windows-sys 0.59.0", 3483 + "dyn-clone", 3484 + "ref-cast", 3485 + "serde", 3486 + "serde_json", 2157 3487 ] 2158 3488 2159 3489 [[package]] ··· 2175 3505 ] 2176 3506 2177 3507 [[package]] 3508 + name = "sec1" 3509 + version = "0.7.3" 3510 + source = "registry+https://github.com/rust-lang/crates.io-index" 3511 + checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 3512 + dependencies = [ 3513 + "base16ct", 3514 + "der", 3515 + "generic-array", 3516 + "pkcs8", 3517 + "subtle", 3518 + "zeroize", 3519 + ] 3520 + 3521 + [[package]] 2178 3522 name = "secp256k1" 2179 3523 version = "0.28.2" 2180 3524 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2193 3537 ] 2194 3538 2195 3539 [[package]] 2196 - name = "security-framework" 2197 - version = "2.11.1" 3540 + name = "serde" 3541 + version = "1.0.228" 2198 3542 source = "registry+https://github.com/rust-lang/crates.io-index" 2199 - checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 3543 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 2200 3544 dependencies = [ 2201 - "bitflags", 2202 - "core-foundation", 2203 - "core-foundation-sys", 2204 - "libc", 2205 - "security-framework-sys", 3545 + "serde_core", 3546 + "serde_derive", 2206 3547 ] 2207 3548 2208 3549 [[package]] 2209 - name = "security-framework-sys" 2210 - version = "2.14.0" 3550 + name = "serde_bytes" 3551 + version = "0.11.19" 2211 3552 source = "registry+https://github.com/rust-lang/crates.io-index" 2212 - checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 3553 + checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" 2213 3554 dependencies = [ 2214 - "core-foundation-sys", 2215 - "libc", 3555 + "serde", 3556 + "serde_core", 2216 3557 ] 2217 3558 2218 3559 [[package]] 2219 - name = "serde" 2220 - version = "1.0.219" 3560 + name = "serde_core" 3561 + version = "1.0.228" 2221 3562 source = "registry+https://github.com/rust-lang/crates.io-index" 2222 - checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 3563 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 2223 3564 dependencies = [ 2224 3565 "serde_derive", 2225 3566 ] 2226 3567 2227 3568 [[package]] 2228 3569 name = "serde_derive" 2229 - version = "1.0.219" 3570 + version = "1.0.228" 2230 3571 source = "registry+https://github.com/rust-lang/crates.io-index" 2231 - checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 3572 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 2232 3573 dependencies = [ 2233 3574 "proc-macro2", 2234 3575 "quote", 2235 - "syn", 3576 + "syn 2.0.105", 3577 + ] 3578 + 3579 + [[package]] 3580 + name = "serde_html_form" 3581 + version = "0.2.8" 3582 + source = "registry+https://github.com/rust-lang/crates.io-index" 3583 + checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" 3584 + dependencies = [ 3585 + "form_urlencoded", 3586 + "indexmap 2.10.0", 3587 + "itoa", 3588 + "ryu", 3589 + "serde_core", 3590 + ] 3591 + 3592 + [[package]] 3593 + name = "serde_ipld_dagcbor" 3594 + version = "0.6.4" 3595 + source = "registry+https://github.com/rust-lang/crates.io-index" 3596 + checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778" 3597 + dependencies = [ 3598 + "cbor4ii", 3599 + "ipld-core", 3600 + "scopeguard", 3601 + "serde", 2236 3602 ] 2237 3603 2238 3604 [[package]] 2239 3605 name = "serde_json" 2240 - version = "1.0.142" 3606 + version = "1.0.145" 2241 3607 source = "registry+https://github.com/rust-lang/crates.io-index" 2242 - checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" 3608 + checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" 2243 3609 dependencies = [ 3610 + "indexmap 2.10.0", 2244 3611 "itoa", 2245 3612 "memchr", 2246 3613 "ryu", 2247 3614 "serde", 3615 + "serde_core", 2248 3616 ] 2249 3617 2250 3618 [[package]] ··· 2258 3626 ] 2259 3627 2260 3628 [[package]] 3629 + name = "serde_repr" 3630 + version = "0.1.20" 3631 + source = "registry+https://github.com/rust-lang/crates.io-index" 3632 + checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" 3633 + dependencies = [ 3634 + "proc-macro2", 3635 + "quote", 3636 + "syn 2.0.105", 3637 + ] 3638 + 3639 + [[package]] 2261 3640 name = "serde_urlencoded" 2262 3641 version = "0.7.1" 2263 3642 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2270 3649 ] 2271 3650 2272 3651 [[package]] 3652 + name = "serde_with" 3653 + version = "3.16.0" 3654 + source = "registry+https://github.com/rust-lang/crates.io-index" 3655 + checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" 3656 + dependencies = [ 3657 + "base64", 3658 + "chrono", 3659 + "hex", 3660 + "indexmap 1.9.3", 3661 + "indexmap 2.10.0", 3662 + "schemars 0.9.0", 3663 + "schemars 1.1.0", 3664 + "serde_core", 3665 + "serde_json", 3666 + "serde_with_macros", 3667 + "time", 3668 + ] 3669 + 3670 + [[package]] 3671 + name = "serde_with_macros" 3672 + version = "3.16.0" 3673 + source = "registry+https://github.com/rust-lang/crates.io-index" 3674 + checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" 3675 + dependencies = [ 3676 + "darling 0.21.3", 3677 + "proc-macro2", 3678 + "quote", 3679 + "syn 2.0.105", 3680 + ] 3681 + 3682 + [[package]] 2273 3683 name = "sha1" 2274 3684 version = "0.10.6" 2275 3685 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2326 3736 ] 2327 3737 2328 3738 [[package]] 3739 + name = "simd-adler32" 3740 + version = "0.3.7" 3741 + source = "registry+https://github.com/rust-lang/crates.io-index" 3742 + checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 3743 + 3744 + [[package]] 2329 3745 name = "slab" 2330 3746 version = "0.4.11" 2331 3747 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2341 3757 ] 2342 3758 2343 3759 [[package]] 3760 + name = "smol_str" 3761 + version = "0.3.4" 3762 + source = "registry+https://github.com/rust-lang/crates.io-index" 3763 + checksum = "3498b0a27f93ef1402f20eefacfaa1691272ac4eca1cdc8c596cb0a245d6cbf5" 3764 + dependencies = [ 3765 + "borsh", 3766 + "serde_core", 3767 + ] 3768 + 3769 + [[package]] 2344 3770 name = "socket2" 2345 3771 version = "0.6.0" 2346 3772 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2403 3829 "crc", 2404 3830 "crossbeam-queue", 2405 3831 "either", 2406 - "event-listener", 3832 + "event-listener 5.4.1", 2407 3833 "futures-core", 2408 3834 "futures-intrusive", 2409 3835 "futures-io", 2410 3836 "futures-util", 2411 3837 "hashbrown 0.15.5", 2412 3838 "hashlink", 2413 - "indexmap", 3839 + "indexmap 2.10.0", 2414 3840 "log", 2415 3841 "memchr", 2416 3842 "once_cell", ··· 2438 3864 "quote", 2439 3865 "sqlx-core", 2440 3866 "sqlx-macros-core", 2441 - "syn", 3867 + "syn 2.0.105", 2442 3868 ] 2443 3869 2444 3870 [[package]] ··· 2449 3875 dependencies = [ 2450 3876 "dotenvy", 2451 3877 "either", 2452 - "heck", 3878 + "heck 0.5.0", 2453 3879 "hex", 2454 3880 "once_cell", 2455 3881 "proc-macro2", ··· 2461 3887 "sqlx-mysql", 2462 3888 "sqlx-postgres", 2463 3889 "sqlx-sqlite", 2464 - "syn", 3890 + "syn 2.0.105", 2465 3891 "tokio", 2466 3892 "url", 2467 3893 ] ··· 2592 4018 ] 2593 4019 2594 4020 [[package]] 4021 + name = "static-regular-grammar" 4022 + version = "2.0.2" 4023 + source = "registry+https://github.com/rust-lang/crates.io-index" 4024 + checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957" 4025 + dependencies = [ 4026 + "abnf", 4027 + "btree-range-map", 4028 + "ciborium", 4029 + "hex_fmt", 4030 + "indoc", 4031 + "proc-macro-error", 4032 + "proc-macro2", 4033 + "quote", 4034 + "serde", 4035 + "sha2", 4036 + "syn 2.0.105", 4037 + "thiserror 1.0.69", 4038 + ] 4039 + 4040 + [[package]] 4041 + name = "static_assertions" 4042 + version = "1.1.0" 4043 + source = "registry+https://github.com/rust-lang/crates.io-index" 4044 + checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 4045 + 4046 + [[package]] 2595 4047 name = "stringprep" 2596 4048 version = "0.1.5" 2597 4049 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2616 4068 2617 4069 [[package]] 2618 4070 name = "syn" 4071 + version = "1.0.109" 4072 + source = "registry+https://github.com/rust-lang/crates.io-index" 4073 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 4074 + dependencies = [ 4075 + "proc-macro2", 4076 + "quote", 4077 + "unicode-ident", 4078 + ] 4079 + 4080 + [[package]] 4081 + name = "syn" 2619 4082 version = "2.0.105" 2620 4083 source = "registry+https://github.com/rust-lang/crates.io-index" 2621 4084 checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619" ··· 2630 4093 version = "1.0.2" 2631 4094 source = "registry+https://github.com/rust-lang/crates.io-index" 2632 4095 checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 4096 + dependencies = [ 4097 + "futures-core", 4098 + ] 2633 4099 2634 4100 [[package]] 2635 4101 name = "synstructure" ··· 2639 4105 dependencies = [ 2640 4106 "proc-macro2", 2641 4107 "quote", 2642 - "syn", 4108 + "syn 2.0.105", 4109 + ] 4110 + 4111 + [[package]] 4112 + name = "system-configuration" 4113 + version = "0.6.1" 4114 + source = "registry+https://github.com/rust-lang/crates.io-index" 4115 + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 4116 + dependencies = [ 4117 + "bitflags", 4118 + "core-foundation", 4119 + "system-configuration-sys", 2643 4120 ] 2644 4121 2645 4122 [[package]] 2646 - name = "tempfile" 2647 - version = "3.21.0" 4123 + name = "system-configuration-sys" 4124 + version = "0.6.0" 2648 4125 source = "registry+https://github.com/rust-lang/crates.io-index" 2649 - checksum = "15b61f8f20e3a6f7e0649d825294eaf317edce30f82cf6026e7e4cb9222a7d1e" 4126 + checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 2650 4127 dependencies = [ 2651 - "fastrand", 2652 - "getrandom 0.3.3", 2653 - "once_cell", 2654 - "rustix", 2655 - "windows-sys 0.59.0", 4128 + "core-foundation-sys", 4129 + "libc", 2656 4130 ] 2657 4131 2658 4132 [[package]] ··· 2681 4155 dependencies = [ 2682 4156 "proc-macro2", 2683 4157 "quote", 2684 - "syn", 4158 + "syn 2.0.105", 2685 4159 ] 2686 4160 2687 4161 [[package]] ··· 2692 4166 dependencies = [ 2693 4167 "proc-macro2", 2694 4168 "quote", 2695 - "syn", 4169 + "syn 2.0.105", 2696 4170 ] 2697 4171 2698 4172 [[package]] ··· 2705 4179 ] 2706 4180 2707 4181 [[package]] 4182 + name = "time" 4183 + version = "0.3.44" 4184 + source = "registry+https://github.com/rust-lang/crates.io-index" 4185 + checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" 4186 + dependencies = [ 4187 + "deranged", 4188 + "itoa", 4189 + "num-conv", 4190 + "powerfmt", 4191 + "serde", 4192 + "time-core", 4193 + "time-macros", 4194 + ] 4195 + 4196 + [[package]] 4197 + name = "time-core" 4198 + version = "0.1.6" 4199 + source = "registry+https://github.com/rust-lang/crates.io-index" 4200 + checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" 4201 + 4202 + [[package]] 4203 + name = "time-macros" 4204 + version = "0.2.24" 4205 + source = "registry+https://github.com/rust-lang/crates.io-index" 4206 + checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" 4207 + dependencies = [ 4208 + "num-conv", 4209 + "time-core", 4210 + ] 4211 + 4212 + [[package]] 2708 4213 name = "tinystr" 2709 4214 version = "0.8.1" 2710 4215 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2756 4261 dependencies = [ 2757 4262 "proc-macro2", 2758 4263 "quote", 2759 - "syn", 4264 + "syn 2.0.105", 2760 4265 ] 2761 4266 2762 4267 [[package]] 2763 - name = "tokio-native-tls" 2764 - version = "0.3.1" 4268 + name = "tokio-rustls" 4269 + version = "0.26.2" 2765 4270 source = "registry+https://github.com/rust-lang/crates.io-index" 2766 - checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 4271 + checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" 2767 4272 dependencies = [ 2768 - "native-tls", 4273 + "rustls", 2769 4274 "tokio", 2770 4275 ] 2771 4276 ··· 2782 4287 2783 4288 [[package]] 2784 4289 name = "tokio-util" 2785 - version = "0.7.15" 4290 + version = "0.7.17" 2786 4291 source = "registry+https://github.com/rust-lang/crates.io-index" 2787 - checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 4292 + checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" 2788 4293 dependencies = [ 2789 4294 "bytes", 2790 4295 "futures-core", ··· 2830 4335 dependencies = [ 2831 4336 "futures-core", 2832 4337 "futures-util", 2833 - "indexmap", 4338 + "indexmap 2.10.0", 2834 4339 "pin-project-lite", 2835 4340 "slab", 2836 4341 "sync_wrapper", ··· 2851 4356 "bitflags", 2852 4357 "bytes", 2853 4358 "futures-core", 4359 + "futures-util", 2854 4360 "http", 2855 4361 "http-body", 4362 + "iri-string", 2856 4363 "pin-project-lite", 2857 4364 "tokio", 2858 4365 "tokio-util", 4366 + "tower", 2859 4367 "tower-layer", 2860 4368 "tower-service", 2861 4369 ] ··· 2909 4417 dependencies = [ 2910 4418 "proc-macro2", 2911 4419 "quote", 2912 - "syn", 4420 + "syn 2.0.105", 2913 4421 ] 2914 4422 2915 4423 [[package]] ··· 2952 4460 ] 2953 4461 2954 4462 [[package]] 4463 + name = "trait-variant" 4464 + version = "0.1.2" 4465 + source = "registry+https://github.com/rust-lang/crates.io-index" 4466 + checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" 4467 + dependencies = [ 4468 + "proc-macro2", 4469 + "quote", 4470 + "syn 2.0.105", 4471 + ] 4472 + 4473 + [[package]] 2955 4474 name = "try-lock" 2956 4475 version = "0.2.5" 2957 4476 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2997 4516 checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 2998 4517 2999 4518 [[package]] 4519 + name = "unicode-segmentation" 4520 + version = "1.12.0" 4521 + source = "registry+https://github.com/rust-lang/crates.io-index" 4522 + checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 4523 + 4524 + [[package]] 4525 + name = "unicode-width" 4526 + version = "0.1.14" 4527 + source = "registry+https://github.com/rust-lang/crates.io-index" 4528 + checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 4529 + 4530 + [[package]] 4531 + name = "unsigned-varint" 4532 + version = "0.8.0" 4533 + source = "registry+https://github.com/rust-lang/crates.io-index" 4534 + checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" 4535 + 4536 + [[package]] 4537 + name = "untrusted" 4538 + version = "0.7.1" 4539 + source = "registry+https://github.com/rust-lang/crates.io-index" 4540 + checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 4541 + 4542 + [[package]] 3000 4543 name = "untrusted" 3001 4544 version = "0.9.0" 3002 4545 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3004 4547 3005 4548 [[package]] 3006 4549 name = "url" 3007 - version = "2.5.4" 4550 + version = "2.5.7" 3008 4551 source = "registry+https://github.com/rust-lang/crates.io-index" 3009 - checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 4552 + checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" 3010 4553 dependencies = [ 3011 4554 "form_urlencoded", 3012 4555 "idna", 3013 4556 "percent-encoding", 4557 + "serde", 3014 4558 ] 3015 4559 3016 4560 [[package]] 4561 + name = "urlencoding" 4562 + version = "2.1.3" 4563 + source = "registry+https://github.com/rust-lang/crates.io-index" 4564 + checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" 4565 + 4566 + [[package]] 4567 + name = "utf8-width" 4568 + version = "0.1.8" 4569 + source = "registry+https://github.com/rust-lang/crates.io-index" 4570 + checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091" 4571 + 4572 + [[package]] 3017 4573 name = "utf8_iter" 3018 4574 version = "1.0.4" 3019 4575 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3024 4580 version = "0.1.1" 3025 4581 source = "registry+https://github.com/rust-lang/crates.io-index" 3026 4582 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 4583 + 4584 + [[package]] 4585 + name = "value-bag" 4586 + version = "1.12.0" 4587 + source = "registry+https://github.com/rust-lang/crates.io-index" 4588 + checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" 3027 4589 3028 4590 [[package]] 3029 4591 name = "vcpkg" ··· 3063 4625 checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 3064 4626 3065 4627 [[package]] 3066 - name = "wasi" 3067 - version = "0.14.2+wasi-0.2.4" 4628 + name = "wasip2" 4629 + version = "1.0.1+wasi-0.2.4" 3068 4630 source = "registry+https://github.com/rust-lang/crates.io-index" 3069 - checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 4631 + checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" 3070 4632 dependencies = [ 3071 - "wit-bindgen-rt", 4633 + "wit-bindgen", 3072 4634 ] 3073 4635 3074 4636 [[package]] ··· 3099 4661 "log", 3100 4662 "proc-macro2", 3101 4663 "quote", 3102 - "syn", 4664 + "syn 2.0.105", 3103 4665 "wasm-bindgen-shared", 3104 4666 ] 3105 4667 3106 4668 [[package]] 4669 + name = "wasm-bindgen-futures" 4670 + version = "0.4.50" 4671 + source = "registry+https://github.com/rust-lang/crates.io-index" 4672 + checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 4673 + dependencies = [ 4674 + "cfg-if", 4675 + "js-sys", 4676 + "once_cell", 4677 + "wasm-bindgen", 4678 + "web-sys", 4679 + ] 4680 + 4681 + [[package]] 3107 4682 name = "wasm-bindgen-macro" 3108 4683 version = "0.2.100" 3109 4684 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3121 4696 dependencies = [ 3122 4697 "proc-macro2", 3123 4698 "quote", 3124 - "syn", 4699 + "syn 2.0.105", 3125 4700 "wasm-bindgen-backend", 3126 4701 "wasm-bindgen-shared", 3127 4702 ] ··· 3136 4711 ] 3137 4712 3138 4713 [[package]] 4714 + name = "wasm-streams" 4715 + version = "0.4.2" 4716 + source = "registry+https://github.com/rust-lang/crates.io-index" 4717 + checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 4718 + dependencies = [ 4719 + "futures-util", 4720 + "js-sys", 4721 + "wasm-bindgen", 4722 + "wasm-bindgen-futures", 4723 + "web-sys", 4724 + ] 4725 + 4726 + [[package]] 3139 4727 name = "web-sys" 3140 4728 version = "0.3.77" 3141 4729 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3171 4759 checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" 3172 4760 dependencies = [ 3173 4761 "rustls-pki-types", 4762 + ] 4763 + 4764 + [[package]] 4765 + name = "which" 4766 + version = "4.4.2" 4767 + source = "registry+https://github.com/rust-lang/crates.io-index" 4768 + checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 4769 + dependencies = [ 4770 + "either", 4771 + "home", 4772 + "once_cell", 4773 + "rustix 0.38.44", 3174 4774 ] 3175 4775 3176 4776 [[package]] ··· 3222 4822 dependencies = [ 3223 4823 "windows-implement", 3224 4824 "windows-interface", 3225 - "windows-link", 4825 + "windows-link 0.1.3", 3226 4826 "windows-result", 3227 4827 "windows-strings", 3228 4828 ] ··· 3235 4835 dependencies = [ 3236 4836 "proc-macro2", 3237 4837 "quote", 3238 - "syn", 4838 + "syn 2.0.105", 3239 4839 ] 3240 4840 3241 4841 [[package]] ··· 3246 4846 dependencies = [ 3247 4847 "proc-macro2", 3248 4848 "quote", 3249 - "syn", 4849 + "syn 2.0.105", 3250 4850 ] 3251 4851 3252 4852 [[package]] ··· 3256 4856 checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" 3257 4857 3258 4858 [[package]] 4859 + name = "windows-link" 4860 + version = "0.2.1" 4861 + source = "registry+https://github.com/rust-lang/crates.io-index" 4862 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 4863 + 4864 + [[package]] 4865 + name = "windows-registry" 4866 + version = "0.5.3" 4867 + source = "registry+https://github.com/rust-lang/crates.io-index" 4868 + checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" 4869 + dependencies = [ 4870 + "windows-link 0.1.3", 4871 + "windows-result", 4872 + "windows-strings", 4873 + ] 4874 + 4875 + [[package]] 3259 4876 name = "windows-result" 3260 4877 version = "0.3.4" 3261 4878 source = "registry+https://github.com/rust-lang/crates.io-index" 3262 4879 checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3263 4880 dependencies = [ 3264 - "windows-link", 4881 + "windows-link 0.1.3", 3265 4882 ] 3266 4883 3267 4884 [[package]] ··· 3270 4887 source = "registry+https://github.com/rust-lang/crates.io-index" 3271 4888 checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3272 4889 dependencies = [ 3273 - "windows-link", 4890 + "windows-link 0.1.3", 3274 4891 ] 3275 4892 3276 4893 [[package]] ··· 3301 4918 ] 3302 4919 3303 4920 [[package]] 4921 + name = "windows-sys" 4922 + version = "0.61.2" 4923 + source = "registry+https://github.com/rust-lang/crates.io-index" 4924 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 4925 + dependencies = [ 4926 + "windows-link 0.2.1", 4927 + ] 4928 + 4929 + [[package]] 3304 4930 name = "windows-targets" 3305 4931 version = "0.48.5" 3306 4932 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3422 5048 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3423 5049 3424 5050 [[package]] 3425 - name = "wit-bindgen-rt" 3426 - version = "0.39.0" 5051 + name = "wit-bindgen" 5052 + version = "0.46.0" 3427 5053 source = "registry+https://github.com/rust-lang/crates.io-index" 3428 - checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 3429 - dependencies = [ 3430 - "bitflags", 3431 - ] 5054 + checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" 3432 5055 3433 5056 [[package]] 3434 5057 name = "writeable" ··· 3437 5060 checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 3438 5061 3439 5062 [[package]] 5063 + name = "yansi" 5064 + version = "1.0.1" 5065 + source = "registry+https://github.com/rust-lang/crates.io-index" 5066 + checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 5067 + 5068 + [[package]] 3440 5069 name = "yoke" 3441 5070 version = "0.8.0" 3442 5071 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3456 5085 dependencies = [ 3457 5086 "proc-macro2", 3458 5087 "quote", 3459 - "syn", 5088 + "syn 2.0.105", 3460 5089 "synstructure", 3461 5090 ] 3462 5091 ··· 3477 5106 dependencies = [ 3478 5107 "proc-macro2", 3479 5108 "quote", 3480 - "syn", 5109 + "syn 2.0.105", 3481 5110 ] 3482 5111 3483 5112 [[package]] ··· 3497 5126 dependencies = [ 3498 5127 "proc-macro2", 3499 5128 "quote", 3500 - "syn", 5129 + "syn 2.0.105", 3501 5130 "synstructure", 3502 5131 ] 3503 5132 ··· 3518 5147 dependencies = [ 3519 5148 "proc-macro2", 3520 5149 "quote", 3521 - "syn", 5150 + "syn 2.0.105", 3522 5151 ] 3523 5152 3524 5153 [[package]] ··· 3551 5180 dependencies = [ 3552 5181 "proc-macro2", 3553 5182 "quote", 3554 - "syn", 5183 + "syn 2.0.105", 3555 5184 ] 3556 5185 3557 5186 [[package]]
+16 -4
Cargo.toml
··· 1 1 [package] 2 2 name = "pds_gatekeeper" 3 - version = "0.1.0" 3 + version = "0.1.2" 4 4 edition = "2024" 5 + license = "MIT" 5 6 6 7 [dependencies] 7 8 axum = { version = "0.8.4", features = ["macros", "json"] } ··· 14 15 tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] } 15 16 hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] } 16 17 tower-http = { version = "0.6", features = ["cors", "compression-zstd"] } 17 - tower_governor = "0.8.0" 18 + tower_governor = { version = "0.8.0", features = ["axum", "tracing"] } 18 19 hex = "0.4" 19 20 jwt-compact = { version = "0.8.0", features = ["es256k"] } 20 21 scrypt = "0.11" 21 - lettre = { version = "0.11.18", features = ["tokio1", "pool", "tokio1-native-tls"] } 22 + #Leaveing these two cause I think it is needed by the email crate for ssl 23 + aws-lc-rs = "1.13.0" 24 + rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] } 25 + lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "sendmail-transport", "tokio1", "tokio1-rustls"] } 22 26 handlebars = { version = "6.3.2", features = ["rust-embed"] } 23 27 rust-embed = "8.7.2" 24 28 axum-template = { version = "3.0.0", features = ["handlebars"] } 25 29 rand = "0.9.2" 26 30 anyhow = "1.0.99" 27 - chrono = "0.4.41" 31 + chrono = { version = "0.4.42", features = ["default", "serde"] } 28 32 sha2 = "0.10" 33 + jacquard-common = "0.9.2" 34 + jacquard-identity = "0.9.2" 35 + multibase = "0.9.2" 36 + reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } 37 + urlencoding = "2.1" 38 + url = "2.5.7" 39 + html-escape = "0.2.13" 40 + josekit = "0.10.3"
+10
Dockerfile
··· 1 + FROM rust:1.89.0-bookworm AS builder 2 + WORKDIR /app 3 + COPY ../ /app 4 + RUN cargo build --release 5 + # 6 + FROM rust:1.89-slim-bookworm AS api 7 + RUN apt-get update 8 + RUN apt-get install -y ca-certificates 9 + COPY --from=builder /app/target/release/pds_gatekeeper /usr/local/bin/pds_gatekeeper 10 + CMD ["pds_gatekeeper"]
+21
LICENSE.md
··· 1 + MIT License 2 + 3 + Copyright (c) 2025 Bailey Townsend 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+171 -12
README.md
··· 15 15 - Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins 16 16 - Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA 17 17 18 - ## Captcha on Create Account 18 + ## Captcha on account creation 19 19 20 - Future feature? 20 + Require a `verificationCode` set on the `createAccount` request. This is gotten from completing a captcha challenge 21 + hosted on the 22 + PDS mimicking what the Bluesky Entryway does. Migration tools will need to support this, but social-apps will support 23 + and redirect to `GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT`. This is how the clients know to get the code to prove a captcha 24 + was successful. 25 + 26 + - Requires `GATEKEEPER_CREATE_ACCOUNT_CAPTCHA` to be set to true. 27 + - Requires `PDS_HCAPTCHA_SITE_KEY` and `PDS_HCAPTCHA_SECRET_KEY` to be set. Can sign up at https://www.hcaptcha.com/ 28 + - Requires proxying `/xrpc/com.atproto.server.describeServer`, `/xrpc/com.atproto.server.createAccount` and `/gate/*` to 29 + PDS 30 + Gatekeeper 31 + - Optional `GATEKEEPER_JWE_KEY` key to encrypt the captcha verification code. Defaults to a random 32 byte key. Not 32 + strictly needed unless you're scaling 33 + - Optional`GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT` default redirect on captcha success. Defaults to `https://bsky.app`. 34 + - Optional `GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS` allowed redirect urls for captcha success. You want these to match the 35 + url showing the captcha. Defaults are: 36 + - https://bsky.app 37 + - https://pdsmoover.com 38 + - https://blacksky.community 39 + - https://tektite.cc 40 + 41 + ## Block account creation unless it's a migration 42 + 43 + You can set `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` to block createAccount unless it's via a migration. This does not require 44 + a change for migration tools, but social-apps create a new account will no longer work and to create a brand new account 45 + users will need to do this via the Oauth account create screen on the PDS. We recommend setting `PDS_HCAPTCHA_SITE_KEY` 46 + and `PDS_HCAPTCHA_SECRET_KEY` so the OAuth screen is protected by a captcha if you use this with invite codes turned 47 + off. 21 48 22 49 # Setup 23 50 24 - We are getting close! Testing now 51 + PDS Gatekeeper has 2 parts to its setup, docker compose file and a reverse proxy (Caddy in this case). I will be 52 + assuming you setup the PDS following the directions 53 + found [here](https://atproto.com/guides/self-hosting), but if yours is different, or you have questions, feel free to 54 + let 55 + me know, and we can figure it out. 56 + 57 + ## Docker compose 58 + 59 + The pds gatekeeper container can be found on docker hub under the name `fatfingers23/pds_gatekeeper`. The container does 60 + need access to the `/pds` root folder to access the same db's as your PDS. The part you need to add would look a bit 61 + like below. You can find a full example of what I use for my pds at [./examples/compose.yml](./examples/compose.yml). 62 + This is usually found at `/pds/compose.yaml`on your PDS> 63 + 64 + ```yml 65 + gatekeeper: 66 + container_name: gatekeeper 67 + image: fatfingers23/pds_gatekeeper:latest 68 + network_mode: host 69 + restart: unless-stopped 70 + #This gives the container to the access to the PDS folder. Source is the location on your server of that directory 71 + volumes: 72 + - type: bind 73 + source: /pds 74 + target: /pds 75 + depends_on: 76 + - pds 77 + ``` 78 + 79 + For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up 80 + correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml). 81 + 82 + ```yml 83 + gatekeeper: 84 + container_name: gatekeeper 85 + image: 'fatfingers23/pds_gatekeeper:latest' 86 + restart: unless-stopped 87 + volumes: 88 + - '/pds:/pds' 89 + environment: 90 + - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 91 + - 'PDS_BASE_URL=http://pds:3000' 92 + - GATEKEEPER_HOST=0.0.0.0 93 + depends_on: 94 + - pds 95 + healthcheck: 96 + test: 97 + - CMD 98 + - timeout 99 + - '1' 100 + - bash 101 + - '-c' 102 + - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080' 103 + interval: 10s 104 + timeout: 5s 105 + retries: 3 106 + start_period: 10s 107 + labels: 108 + - traefik.enable=true 109 + - 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))' 110 + - traefik.http.routers.pds-gatekeeper.entrypoints=https 111 + - traefik.http.routers.pds-gatekeeper.tls=true 112 + - traefik.http.routers.pds-gatekeeper.priority=100 113 + - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors 114 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080 115 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http 116 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH' 117 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*' 118 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*' 119 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100 120 + - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true 121 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true 122 + ``` 25 123 26 - Nothing here yet! If you are brave enough to try before full release, let me know and I'll help you set it up. 27 - But I want to run it locally on my own PDS first to test run it a bit. 124 + ## Caddy setup 28 125 29 - Example Caddyfile (mostly so I don't lose it for now. Will have a better one in the future) 126 + For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add 127 + in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile). 128 + This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS. 30 129 31 - ```caddyfile 32 - http://localhost { 130 + ``` 131 + @gatekeeper { 132 + path /xrpc/com.atproto.server.getSession 133 + path /xrpc/com.atproto.server.describeServer 134 + path /xrpc/com.atproto.server.updateEmail 135 + path /xrpc/com.atproto.server.createSession 136 + path /xrpc/com.atproto.server.createAccount 137 + path /@atproto/oauth-provider/~api/sign-in 138 + path /gate/* 139 + } 140 + 141 + handle @gatekeeper { 142 + reverse_proxy http://localhost:8080 143 + } 33 144 145 + reverse_proxy http://localhost:3000 146 + ``` 147 + 148 + If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to 149 + `localhost:8081` (or w/e port you want). 150 + 151 + ``` 152 + http://*.localhost:8082, http://localhost:8082 { 34 153 @gatekeeper { 35 154 path /xrpc/com.atproto.server.getSession 155 + path /xrpc/com.atproto.server.describeServer 36 156 path /xrpc/com.atproto.server.updateEmail 37 157 path /xrpc/com.atproto.server.createSession 158 + path /xrpc/com.atproto.server.createAccount 38 159 path /@atproto/oauth-provider/~api/sign-in 160 + path /gate/* 39 161 } 40 162 41 163 handle @gatekeeper { 42 - reverse_proxy http://localhost:8080 164 + #This is the address for PDS gatekeeper, default is 8080 165 + reverse_proxy http://localhost:8080 166 + #Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper 167 + header_up X-Forwarded-For {http.request.header.CF-Connecting-IP} 43 168 } 169 + reverse_proxy http://localhost:3000 170 + } 171 + 172 + ``` 173 + 174 + # Environment variables and bonuses 175 + 176 + Every environment variable can be set in the `pds.env` and shared between PDS and gatekeeper and the PDS, with the 177 + exception of `PDS_ENV_LOCATION`. This can be set to load the pds.env, by default it checks `/pds/pds.env` and is 178 + recommended to mount the `/pds` folder on the server to `/pds` in the pds gatekeeper container. 179 + 180 + `PDS_DATA_DIRECTORY` - Root directory of the PDS. Same as the one found in `pds.env` this is how pds gatekeeper knows 181 + knows the rest of the environment variables. 182 + 183 + `GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY` - The folder for templates of the emails PDS gatekeeper sends. You can find them 184 + in [./email_templates](./email_templates). You are free to edit them as you please and set this variable to a location 185 + in the pds gateekeper container and it will use them in place of the default ones. Just make sure ot keep the names the 186 + same. 44 187 45 - reverse_proxy /* http://localhost:3000 46 - } 188 + `GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT` - Subject of the email sent to the user when they turn on 2FA. Defaults to 189 + `Sign in to Bluesky` 190 + 191 + `PDS_BASE_URL` - Base url of the PDS. You most likely want `https://localhost:3000` which is also the default 192 + 193 + `GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1` 194 + 195 + `GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080` 47 196 48 - ``` 197 + `GATEKEEPER_CREATE_ACCOUNT_PER_SECOND` - Sets how often it takes a count off the limiter. example if you hit the rate 198 + limit of 5 and set to 60, then in 60 seconds you will be able to make one more. Or in 5 minutes be able to make 5 more. 199 + 200 + `GATEKEEPER_CREATE_ACCOUNT_BURST` - Sets how many requests can be made in a burst. In the prior example this is where 201 + the 5 comes from. Example can set this to 10 to allow for 10 requests in a burst, and after 60 seconds it will drop one 202 + off. 203 + 204 + `GATEKEEPER_ALLOW_ONLY_MIGRATIONS` - Defaults false. If set to true, will only allow the 205 + `/xrpc/com.atproto.server.createAccount` endpoint to be used for migrations. Meaning it will check for the serviceAuth 206 + token and verify it is valid. 207 +
+30
examples/Caddyfile
··· 1 + { 2 + email youremail@myemail.com 3 + on_demand_tls { 4 + ask http://localhost:3000/tls-check 5 + } 6 + } 7 + 8 + *.yourpds.com, yourpds.com { 9 + tls { 10 + on_demand 11 + } 12 + # You'll most likely just want from here to.... 13 + @gatekeeper { 14 + path /xrpc/com.atproto.server.getSession 15 + path /xrpc/com.atproto.server.describeServer 16 + path /xrpc/com.atproto.server.updateEmail 17 + path /xrpc/com.atproto.server.createSession 18 + path /xrpc/com.atproto.server.createAccount 19 + path /@atproto/oauth-provider/~api/sign-in 20 + path /gate/* 21 + } 22 + 23 + handle @gatekeeper { 24 + #This is the address for PDS gatekeeper, default is 8080 25 + reverse_proxy http://localhost:8080 26 + } 27 + 28 + reverse_proxy http://localhost:3000 29 + #..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line 30 + }
+51
examples/compose.yml
··· 1 + version: '3.9' 2 + services: 3 + caddy: 4 + container_name: caddy 5 + image: caddy:2 6 + network_mode: host 7 + depends_on: 8 + - pds 9 + restart: unless-stopped 10 + volumes: 11 + - type: bind 12 + source: /pds/caddy/data 13 + target: /data 14 + - type: bind 15 + source: /pds/caddy/etc/caddy 16 + target: /etc/caddy 17 + pds: 18 + container_name: pds 19 + image: ghcr.io/bluesky-social/pds:0.4 20 + network_mode: host 21 + restart: unless-stopped 22 + volumes: 23 + - type: bind 24 + source: /pds 25 + target: /pds 26 + env_file: 27 + - /pds/pds.env 28 + watchtower: 29 + container_name: watchtower 30 + image: containrrr/watchtower:latest 31 + network_mode: host 32 + volumes: 33 + - type: bind 34 + source: /var/run/docker.sock 35 + target: /var/run/docker.sock 36 + restart: unless-stopped 37 + environment: 38 + WATCHTOWER_CLEANUP: true 39 + WATCHTOWER_SCHEDULE: "@midnight" 40 + gatekeeper: 41 + container_name: gatekeeper 42 + image: fatfingers23/pds_gatekeeper:latest 43 + network_mode: host 44 + restart: unless-stopped 45 + #This gives the container to the access to the PDS folder. Source is the location on your server of that directory 46 + volumes: 47 + - type: bind 48 + source: /pds 49 + target: /pds 50 + depends_on: 51 + - pds
+73
examples/coolify-compose.yml
··· 1 + services: 2 + pds: 3 + image: 'ghcr.io/bluesky-social/pds:0.4.182' 4 + volumes: 5 + - '/pds:/pds' 6 + environment: 7 + - SERVICE_URL_PDS_3000 8 + - 'PDS_HOSTNAME=${SERVICE_FQDN_PDS_3000}' 9 + - 'PDS_JWT_SECRET=${SERVICE_HEX_32_JWTSECRET}' 10 + - 'PDS_ADMIN_PASSWORD=${SERVICE_PASSWORD_ADMIN}' 11 + - 'PDS_ADMIN_EMAIL=${PDS_ADMIN_EMAIL}' 12 + - 'PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=${SERVICE_HEX_32_ROTATIONKEY}' 13 + - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 14 + - 'PDS_BLOBSTORE_DISK_LOCATION=${PDS_DATA_DIRECTORY:-/pds}/blocks' 15 + - 'PDS_BLOB_UPLOAD_LIMIT=${PDS_BLOB_UPLOAD_LIMIT:-104857600}' 16 + - 'PDS_DID_PLC_URL=${PDS_DID_PLC_URL:-https://plc.directory}' 17 + - 'PDS_EMAIL_FROM_ADDRESS=${PDS_EMAIL_FROM_ADDRESS}' 18 + - 'PDS_EMAIL_SMTP_URL=${PDS_EMAIL_SMTP_URL}' 19 + - 'PDS_BSKY_APP_VIEW_URL=${PDS_BSKY_APP_VIEW_URL:-https://api.bsky.app}' 20 + - 'PDS_BSKY_APP_VIEW_DID=${PDS_BSKY_APP_VIEW_DID:-did:web:api.bsky.app}' 21 + - 'PDS_REPORT_SERVICE_URL=${PDS_REPORT_SERVICE_URL:-https://mod.bsky.app/xrpc/com.atproto.moderation.createReport}' 22 + - 'PDS_REPORT_SERVICE_DID=${PDS_REPORT_SERVICE_DID:-did:plc:ar7c4by46qjdydhdevvrndac}' 23 + - 'PDS_CRAWLERS=${PDS_CRAWLERS:-https://bsky.network}' 24 + - 'LOG_ENABLED=${LOG_ENABLED:-true}' 25 + command: "sh -c '\n set -euo pipefail\n echo \"Installing required packages and pdsadmin...\"\n apk add --no-cache openssl curl bash jq coreutils gnupg util-linux-misc >/dev/null\n curl -o /usr/local/bin/pdsadmin.sh https://raw.githubusercontent.com/bluesky-social/pds/main/pdsadmin.sh\n chmod 700 /usr/local/bin/pdsadmin.sh\n ln -sf /usr/local/bin/pdsadmin.sh /usr/local/bin/pdsadmin\n echo \"Creating an empty pds.env file so pdsadmin works...\"\n touch ${PDS_DATA_DIRECTORY}/pds.env\n echo \"Launching PDS, enjoy!...\"\n exec node --enable-source-maps index.js\n'\n" 26 + healthcheck: 27 + test: 28 + - CMD 29 + - wget 30 + - '--spider' 31 + - 'http://127.0.0.1:3000/xrpc/_health' 32 + interval: 5s 33 + timeout: 10s 34 + retries: 10 35 + gatekeeper: 36 + container_name: gatekeeper 37 + image: 'fatfingers23/pds_gatekeeper:latest' 38 + restart: unless-stopped 39 + volumes: 40 + - '/pds:/pds' 41 + environment: 42 + - 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}' 43 + - 'PDS_BASE_URL=http://pds:3000' 44 + - GATEKEEPER_HOST=0.0.0.0 45 + depends_on: 46 + - pds 47 + healthcheck: 48 + test: 49 + - CMD 50 + - timeout 51 + - '1' 52 + - bash 53 + - '-c' 54 + - 'cat < /dev/null > /dev/tcp/0.0.0.0/8080' 55 + interval: 10s 56 + timeout: 5s 57 + retries: 3 58 + start_period: 10s 59 + labels: 60 + - traefik.enable=true 61 + - 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.describeServer`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`) || Path(`/gate`))' 62 + - traefik.http.routers.pds-gatekeeper.entrypoints=https 63 + - traefik.http.routers.pds-gatekeeper.tls=true 64 + - traefik.http.routers.pds-gatekeeper.priority=100 65 + - traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors 66 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080 67 + - traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http 68 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH' 69 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*' 70 + - 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*' 71 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100 72 + - traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true 73 + - traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
+166
html_templates/captcha.hbs
··· 1 + <html lang="en" class=" "> 2 + <head> 3 + <meta charset="utf-8"/> 4 + <meta 5 + name="viewport" 6 + content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover" 7 + /> 8 + <meta name="referrer" content="origin-when-cross-origin"/> 9 + 10 + <title> 11 + {{pds}} - Captcha 12 + </title> 13 + <style> 14 + :root, 15 + :root.light-mode { 16 + --brand-color: rgb(16, 131, 254); 17 + --primary-color: rgb(7, 10, 13); 18 + --secondary-color: rgb(66, 86, 108); 19 + --bg-primary-color: rgb(255, 255, 255); 20 + --bg-secondary-color: rgb(240, 242, 245); 21 + } 22 + 23 + @media (prefers-color-scheme: dark) { 24 + :root { 25 + --brand-color: rgb(16, 131, 254); 26 + --primary-color: rgb(255, 255, 255); 27 + --secondary-color: rgb(133, 152, 173); 28 + --bg-primary-color: rgb(7, 10, 13); 29 + --bg-secondary-color: rgb(13, 18, 23); 30 + } 31 + } 32 + 33 + :root.dark-mode { 34 + --brand-color: rgb(16, 131, 254); 35 + --primary-color: rgb(255, 255, 255); 36 + --secondary-color: rgb(133, 152, 173); 37 + --bg-primary-color: rgb(7, 10, 13); 38 + --bg-secondary-color: rgb(13, 18, 23); 39 + } 40 + 41 + body { 42 + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, 43 + Arial, sans-serif; 44 + background: var(--bg-primary-color); 45 + color: var(--primary-color); 46 + text-rendering: optimizeLegibility; 47 + -webkit-font-smoothing: antialiased; 48 + } 49 + 50 + .info { 51 + border-radius: 8px; 52 + padding: 12px 14px; 53 + letter-spacing: 0.25px; 54 + font-weight: 400; 55 + background-color: var(--bg-secondary-color); 56 + color: var(--secondary-color); 57 + } 58 + 59 + .gate-page, 60 + .error-page { 61 + margin: 0; 62 + margin-top: 10px; 63 + display: flex; 64 + justify-content: center; 65 + } 66 + 67 + .gate-page .main, 68 + .error-page .main { 69 + max-width: 600px; 70 + width: 100%; 71 + display: flex; 72 + flex-direction: column; 73 + } 74 + 75 + .gate-page .main { 76 + padding: 0; 77 + } 78 + 79 + .error-page .main { 80 + padding: 20px; 81 + } 82 + 83 + .gate-page .main > :not(:first-child), 84 + .error-page .main > :not(:first-child) { 85 + margin-top: 20px; 86 + } 87 + 88 + .gate-page #gate-form { 89 + margin: 0; 90 + } 91 + 92 + .gate-page #hcaptcha { 93 + display: flex; 94 + justify-content: center; 95 + } 96 + 97 + .pds-title { 98 + font-size: 2.25rem; 99 + font-weight: 700; 100 + line-height: 1.2; 101 + text-align: center; 102 + color: var(--primary-color); 103 + margin: 10px 0 16px; 104 + } 105 + 106 + </style> 107 + 108 + <script type="text/javascript"> 109 + function onCaptchaReady() { 110 + const theme = document.documentElement.classList.contains('dark-mode') ? 111 + 'dark' : 112 + document.documentElement.classList.contains('light-mode') ? 113 + 'light' : 114 + window.matchMedia('(prefers-color-scheme: dark)').matches ? 115 + 'dark' : 116 + 'light' 117 + hcaptcha.render('hcaptcha', {theme}); 118 + } 119 + 120 + function onCaptchaComplete() { 121 + setTimeout(function () { 122 + document.getElementById('gate-form').submit(); 123 + }, 1000); 124 + } 125 + 126 + function onCaptchaError() { 127 + const url = new URL(location.href); 128 + url.searchParams.set('error', 'true'); 129 + location.assign(url.search); 130 + } 131 + 132 + function onCaptchaExpired() { 133 + const url = new URL(location.href); 134 + url.searchParams.set('error', 'expired'); 135 + location.assign(url.search); 136 + } 137 + </script> 138 + <script 139 + src="https://js.hcaptcha.com/1/api.js?render=explicit&onload=onCaptchaReady" 140 + async 141 + defer 142 + ></script> 143 + <link rel="stylesheet" href="/gate/signup/assets/common.css"/> 144 + </head> 145 + 146 + <body class="gate-page"> 147 + <div class="main"> 148 + <div class="pds-title">{{pds}}</div> 149 + <form id="gate-form" action="" method="POST"> 150 + <div 151 + id="hcaptcha" 152 + data-sitekey="{{captcha_site_key}}" 153 + data-callback="onCaptchaComplete" 154 + data-error-callback="onCaptchaError" 155 + data-expired-callback="onCaptchaExpired" 156 + data-chalexpired-callback="onCaptchaExpired" 157 + ></div> 158 + <input type="hidden" name="redirect_url" value="{{redirect_url}}"/> 159 + </form> 160 + {{#if error_message }} 161 + <div class="info">{{error_message}}</div> 162 + {{/if}} 163 + 164 + </div> 165 + </body> 166 + </html>
+6
justfile
··· 1 + release: 2 + docker buildx build \ 3 + --platform linux/arm64,linux/amd64 \ 4 + --tag fatfingers23/pds_gatekeeper:latest \ 5 + --tag fatfingers23/pds_gatekeeper:0.1.0.5 \ 6 + --push .
+10
migrations/20251126000000_gate_codes.sql
··· 1 + -- Add migration script here 2 + CREATE TABLE IF NOT EXISTS gate_codes 3 + ( 4 + code VARCHAR PRIMARY KEY, 5 + handle VARCHAR NOT NULL, 6 + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP 7 + ); 8 + 9 + -- Index on created_at for efficient cleanup of expired codes 10 + CREATE INDEX IF NOT EXISTS idx_gate_codes_created_at ON gate_codes(created_at);
+247
src/gate.rs
··· 1 + use crate::AppState; 2 + use crate::helpers::{generate_gate_token, json_error_response}; 3 + use axum::Form; 4 + use axum::extract::{Query, State}; 5 + use axum::http::StatusCode; 6 + use axum::response::{IntoResponse, Redirect, Response}; 7 + use axum_template::RenderHtml; 8 + use chrono::{DateTime, Utc}; 9 + use serde::{Deserialize, Serialize}; 10 + use std::env; 11 + use tracing::log; 12 + 13 + #[derive(Deserialize)] 14 + pub struct GateQuery { 15 + handle: String, 16 + state: String, 17 + #[serde(default)] 18 + error: Option<String>, 19 + #[serde(default)] 20 + redirect_url: Option<String>, 21 + } 22 + 23 + #[derive(Deserialize, Serialize)] 24 + pub struct CaptchaPage { 25 + handle: String, 26 + state: String, 27 + captcha_site_key: String, 28 + error_message: Option<String>, 29 + pds: String, 30 + redirect_url: Option<String>, 31 + } 32 + 33 + #[derive(Deserialize)] 34 + pub struct CaptchaForm { 35 + #[serde(rename = "h-captcha-response")] 36 + h_captcha_response: String, 37 + #[serde(default)] 38 + redirect_url: Option<String>, 39 + } 40 + 41 + /// GET /gate - Display the captcha page 42 + pub async fn get_gate( 43 + Query(params): Query<GateQuery>, 44 + State(state): State<AppState>, 45 + ) -> impl IntoResponse { 46 + let hcaptcha_site_key = match env::var("PDS_HCAPTCHA_SITE_KEY") { 47 + Ok(key) => key, 48 + Err(_) => { 49 + return json_error_response( 50 + StatusCode::INTERNAL_SERVER_ERROR, 51 + "ServerError", 52 + "hCaptcha is not configured", 53 + ) 54 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 55 + } 56 + }; 57 + 58 + let error_message = match params.error { 59 + None => None, 60 + Some(error) => Some(html_escape::encode_safe(&error).to_string()), 61 + }; 62 + 63 + RenderHtml( 64 + "captcha.hbs", 65 + state.template_engine, 66 + CaptchaPage { 67 + handle: params.handle, 68 + state: params.state, 69 + captcha_site_key: hcaptcha_site_key, 70 + error_message, 71 + pds: state.app_config.pds_service_did.replace("did:web:", ""), 72 + redirect_url: params.redirect_url, 73 + }, 74 + ) 75 + .into_response() 76 + } 77 + 78 + /// POST /gate - Verify captcha and redirect 79 + pub async fn post_gate( 80 + State(state): State<AppState>, 81 + Query(params): Query<GateQuery>, 82 + Form(form): Form<CaptchaForm>, 83 + ) -> Response { 84 + // Verify hCaptcha response 85 + let hcaptcha_secret = match env::var("PDS_HCAPTCHA_SECRET_KEY") { 86 + Ok(secret) => secret, 87 + Err(_) => { 88 + return json_error_response( 89 + StatusCode::INTERNAL_SERVER_ERROR, 90 + "ServerError", 91 + "hCaptcha is not configured", 92 + ) 93 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 94 + } 95 + }; 96 + 97 + let client = match reqwest::Client::builder() 98 + .timeout(std::time::Duration::from_secs(10)) 99 + .build() 100 + { 101 + Ok(c) => c, 102 + Err(e) => { 103 + log::error!("Failed to create HTTP client: {}", e); 104 + return json_error_response( 105 + StatusCode::INTERNAL_SERVER_ERROR, 106 + "ServerError", 107 + "Failed to verify captcha", 108 + ) 109 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 110 + } 111 + }; 112 + 113 + #[derive(Deserialize, Serialize)] 114 + struct HCaptchaResponse { 115 + success: bool, 116 + challenge_ts: DateTime<Utc>, 117 + hostname: String, 118 + #[serde(rename = "error-codes", default)] 119 + error_codes: Vec<String>, 120 + } 121 + 122 + let verification_result = client 123 + .post("https://api.hcaptcha.com/siteverify") 124 + .form(&[ 125 + ("secret", hcaptcha_secret.as_str()), 126 + ("response", form.h_captcha_response.as_str()), 127 + ]) 128 + .send() 129 + .await; 130 + 131 + let verification_response = match verification_result { 132 + Ok(resp) => resp, 133 + Err(e) => { 134 + log::error!("Failed to verify hCaptcha: {}", e); 135 + 136 + return Redirect::to(&format!( 137 + "/gate?handle={}&state={}&error={}", 138 + url_encode(&params.handle), 139 + url_encode(&params.state), 140 + url_encode("Verification failed. Please try again.") 141 + )) 142 + .into_response(); 143 + } 144 + }; 145 + 146 + let captcha_result: HCaptchaResponse = match verification_response.json().await { 147 + Ok(result) => result, 148 + Err(e) => { 149 + log::error!("Failed to parse hCaptcha response: {}", e); 150 + 151 + return Redirect::to(&format!( 152 + "/gate?handle={}&state={}&error={}", 153 + url_encode(&params.handle), 154 + url_encode(&params.state), 155 + url_encode("Verification failed. Please try again.") 156 + )) 157 + .into_response(); 158 + } 159 + }; 160 + 161 + if !captcha_result.success { 162 + log::warn!( 163 + "hCaptcha verification failed for handle {}: {:?}", 164 + params.handle, 165 + captcha_result.error_codes 166 + ); 167 + return Redirect::to(&format!( 168 + "/gate?handle={}&state={}&error={}", 169 + url_encode(&params.handle), 170 + url_encode(&params.state), 171 + url_encode("Verification failed. Please try again.") 172 + )) 173 + .into_response(); 174 + } 175 + 176 + // Generate secure JWE verification token 177 + let code = match generate_gate_token(&params.handle, &state.app_config.gate_jwe_key) { 178 + Ok(token) => token, 179 + Err(e) => { 180 + log::error!("Failed to generate gate token: {}", e); 181 + return json_error_response( 182 + StatusCode::INTERNAL_SERVER_ERROR, 183 + "ServerError", 184 + "Failed to create verification code", 185 + ) 186 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 187 + } 188 + }; 189 + 190 + let now = Utc::now(); 191 + 192 + // Store the encrypted token in the database 193 + let result = sqlx::query( 194 + "INSERT INTO gate_codes (code, handle, created_at) 195 + VALUES (?, ?, ?)", 196 + ) 197 + .bind(&code) 198 + .bind(&params.handle) 199 + .bind(now) 200 + .execute(&state.pds_gatekeeper_pool) 201 + .await; 202 + 203 + if let Err(e) = result { 204 + log::error!("Failed to store gate code: {}", e); 205 + return json_error_response( 206 + StatusCode::INTERNAL_SERVER_ERROR, 207 + "ServerError", 208 + "Failed to create verification code", 209 + ) 210 + .unwrap_or_else(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response()); 211 + } 212 + 213 + // Redirects by origin if it's found. If not redirect to the configured URL. 214 + let mut base_redirect = state.app_config.default_successful_redirect_url.clone(); 215 + if let Some(ref redirect_url) = form.redirect_url { 216 + let trimmed = redirect_url.trim(); 217 + if !trimmed.is_empty() 218 + && (trimmed.starts_with("https://") || trimmed.starts_with("http://")) 219 + { 220 + base_redirect = trimmed.trim_end_matches('/').to_string(); 221 + } 222 + } 223 + 224 + let base_redirect = match state 225 + .app_config 226 + .captcha_success_redirects 227 + .contains(&base_redirect) 228 + { 229 + true => base_redirect, 230 + false => state.app_config.default_successful_redirect_url.clone(), 231 + }; 232 + 233 + // Redirect to client app with code and state 234 + let redirect_url = format!( 235 + "{}/?code={}&state={}", 236 + base_redirect, 237 + url_encode(&code), 238 + url_encode(&params.state) 239 + ); 240 + 241 + Redirect::to(&redirect_url).into_response() 242 + } 243 + 244 + /// Simple URL encode function 245 + fn url_encode(s: &str) -> String { 246 + urlencoding::encode(s).to_string() 247 + }
+177 -14
src/helpers.rs
··· 1 1 use crate::AppState; 2 2 use crate::helpers::TokenCheckError::InvalidToken; 3 3 use anyhow::anyhow; 4 - use axum::body::{Body, to_bytes}; 5 - use axum::extract::Request; 6 - use axum::http::header::CONTENT_TYPE; 7 - use axum::http::{HeaderMap, StatusCode, Uri}; 8 - use axum::response::{IntoResponse, Response}; 4 + use axum::{ 5 + body::{Body, to_bytes}, 6 + extract::Request, 7 + http::header::CONTENT_TYPE, 8 + http::{HeaderMap, StatusCode, Uri}, 9 + response::{IntoResponse, Response}, 10 + }; 9 11 use axum_template::TemplateEngine; 10 12 use chrono::Utc; 11 - use lettre::message::{MultiPart, SinglePart, header}; 12 - use lettre::{AsyncTransport, Message}; 13 + use jacquard_common::{ 14 + service_auth, service_auth::PublicKey, types::did::Did, types::did_doc::VerificationMethod, 15 + types::nsid::Nsid, 16 + }; 17 + use jacquard_identity::{PublicResolver, resolver::IdentityResolver}; 18 + use josekit::jwe::alg::direct::DirectJweAlgorithm; 19 + use lettre::{ 20 + Message, 21 + message::{MultiPart, SinglePart, header}, 22 + }; 13 23 use rand::Rng; 14 24 use serde::de::DeserializeOwned; 15 25 use serde_json::{Map, Value}; 16 26 use sha2::{Digest, Sha256}; 17 27 use sqlx::SqlitePool; 28 + use std::sync::Arc; 18 29 use tracing::{error, log}; 19 30 20 31 ///Used to generate the email 2fa code ··· 39 50 where 40 51 T: DeserializeOwned, 41 52 { 42 - let uri = format!("{}{}", state.pds_base_url, path); 53 + let uri = format!("{}{}", state.app_config.pds_base_url, path); 43 54 *req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?; 44 55 45 56 let result = state ··· 134 145 full_code.push(UPPERCASE_BASE32_CHARS[idx] as char); 135 146 } 136 147 137 - //The PDS implementation creates in lowercase, then converts to uppercase. 138 - //Just going a head and doing uppercase here. 139 - let slice_one = &full_code[0..5].to_ascii_uppercase(); 140 - let slice_two = &full_code[5..10].to_ascii_uppercase(); 148 + let slice_one = &full_code[0..5]; 149 + let slice_two = &full_code[5..10]; 141 150 format!("{slice_one}-{slice_two}") 142 151 } 143 152 ··· 337 346 338 347 let email_message = Message::builder() 339 348 //TODO prob get the proper type in the state 340 - .from(state.mailer_from.parse()?) 349 + .from(state.app_config.mailer_from.parse()?) 341 350 .to(email.parse()?) 342 - .subject("Sign in to Bluesky") 351 + .subject(&state.app_config.email_subject) 343 352 .multipart( 344 353 MultiPart::alternative() // This is composed of two parts. 345 354 .singlepart( ··· 522 531 523 532 format!("{masked_local}@{masked_domain}") 524 533 } 534 + 535 + pub enum VerifyServiceAuthError { 536 + AuthFailed, 537 + Error(anyhow::Error), 538 + } 539 + 540 + /// Verifies the service auth token that is appended to an XRPC proxy request 541 + pub async fn verify_service_auth( 542 + jwt: &str, 543 + lxm: &Nsid<'static>, 544 + public_resolver: Arc<PublicResolver>, 545 + service_did: &Did<'static>, 546 + //The did of the user wanting to create an account 547 + requested_did: &Did<'static>, 548 + ) -> Result<(), VerifyServiceAuthError> { 549 + let parsed = 550 + service_auth::parse_jwt(jwt).map_err(|e| VerifyServiceAuthError::Error(e.into()))?; 551 + 552 + let claims = parsed.claims(); 553 + 554 + let did_doc = public_resolver 555 + .resolve_did_doc(&requested_did) 556 + .await 557 + .map_err(|err| { 558 + log::error!("Error resolving the service auth for: {}", claims.iss); 559 + return VerifyServiceAuthError::Error(err.into()); 560 + })?; 561 + 562 + // Parse the DID document response to get verification methods 563 + let doc = did_doc.parse().map_err(|err| { 564 + log::error!("Error parsing the service auth did doc: {}", claims.iss); 565 + VerifyServiceAuthError::Error(anyhow::anyhow!(err)) 566 + })?; 567 + 568 + let verification_methods = doc.verification_method.as_deref().ok_or_else(|| { 569 + VerifyServiceAuthError::Error(anyhow::anyhow!( 570 + "No verification methods in did doc: {}", 571 + &claims.iss 572 + )) 573 + })?; 574 + 575 + let signing_key = extract_signing_key(verification_methods).ok_or_else(|| { 576 + VerifyServiceAuthError::Error(anyhow::anyhow!( 577 + "No signing key found in did doc: {}", 578 + &claims.iss 579 + )) 580 + })?; 581 + 582 + service_auth::verify_signature(&parsed, &signing_key).map_err(|err| { 583 + log::error!("Error verifying service auth signature: {}", err); 584 + VerifyServiceAuthError::AuthFailed 585 + })?; 586 + 587 + // Now validate claims (audience, expiration, etc.) 588 + claims.validate(service_did).map_err(|e| { 589 + log::error!("Error validating service auth claims: {}", e); 590 + VerifyServiceAuthError::AuthFailed 591 + })?; 592 + 593 + if claims.aud != *service_did { 594 + log::error!("Invalid audience (did:web): {}", claims.aud); 595 + return Err(VerifyServiceAuthError::AuthFailed); 596 + } 597 + 598 + let lxm_from_claims = claims.lxm.as_ref().ok_or_else(|| { 599 + VerifyServiceAuthError::Error(anyhow::anyhow!("No lxm claim in service auth JWT")) 600 + })?; 601 + 602 + if lxm_from_claims != lxm { 603 + return Err(VerifyServiceAuthError::Error(anyhow::anyhow!( 604 + "Invalid XRPC endpoint requested" 605 + ))); 606 + } 607 + Ok(()) 608 + } 609 + 610 + /// Ripped from Jacquard 611 + /// 612 + /// Extract the signing key from a DID document's verification methods. 613 + /// 614 + /// This looks for a key with type "atproto" or the first available key 615 + /// if no atproto-specific key is found. 616 + fn extract_signing_key(methods: &[VerificationMethod]) -> Option<PublicKey> { 617 + // First try to find an atproto-specific key 618 + let atproto_method = methods 619 + .iter() 620 + .find(|m| m.r#type.as_ref() == "Multikey" || m.r#type.as_ref() == "atproto"); 621 + 622 + let method = atproto_method.or_else(|| methods.first())?; 623 + 624 + // Parse the multikey 625 + let public_key_multibase = method.public_key_multibase.as_ref()?; 626 + 627 + // Decode multibase 628 + let (_, key_bytes) = multibase::decode(public_key_multibase.as_ref()).ok()?; 629 + 630 + // First two bytes are the multicodec prefix 631 + if key_bytes.len() < 2 { 632 + return None; 633 + } 634 + 635 + let codec = &key_bytes[..2]; 636 + let key_material = &key_bytes[2..]; 637 + 638 + match codec { 639 + // p256-pub (0x1200) 640 + [0x80, 0x24] => PublicKey::from_p256_bytes(key_material).ok(), 641 + // secp256k1-pub (0xe7) 642 + [0xe7, 0x01] => PublicKey::from_k256_bytes(key_material).ok(), 643 + _ => None, 644 + } 645 + } 646 + 647 + /// Payload for gate JWE tokens 648 + #[derive(serde::Serialize, serde::Deserialize, Debug)] 649 + pub struct GateTokenPayload { 650 + pub handle: String, 651 + pub created_at: String, 652 + } 653 + 654 + /// Generate a secure JWE token for gate verification 655 + pub fn generate_gate_token(handle: &str, encryption_key: &[u8]) -> Result<String, anyhow::Error> { 656 + use josekit::jwe::{JweHeader, alg::direct::DirectJweAlgorithm}; 657 + 658 + let payload = GateTokenPayload { 659 + handle: handle.to_string(), 660 + created_at: Utc::now().to_rfc3339(), 661 + }; 662 + 663 + let payload_json = serde_json::to_string(&payload)?; 664 + 665 + let mut header = JweHeader::new(); 666 + header.set_token_type("JWT"); 667 + header.set_content_encryption("A128CBC-HS256"); 668 + 669 + let encrypter = DirectJweAlgorithm::Dir.encrypter_from_bytes(encryption_key)?; 670 + 671 + // Encrypt 672 + let jwe = josekit::jwe::serialize_compact(payload_json.as_bytes(), &header, &encrypter)?; 673 + 674 + Ok(jwe) 675 + } 676 + 677 + /// Verify and decrypt a gate JWE token, returning the payload if valid 678 + pub fn verify_gate_token( 679 + token: &str, 680 + encryption_key: &[u8], 681 + ) -> Result<GateTokenPayload, anyhow::Error> { 682 + let decrypter = DirectJweAlgorithm::Dir.decrypter_from_bytes(encryption_key)?; 683 + let (payload_bytes, _header) = josekit::jwe::deserialize_compact(token, &decrypter)?; 684 + let payload: GateTokenPayload = serde_json::from_slice(&payload_bytes)?; 685 + 686 + Ok(payload) 687 + }
+254 -40
src/main.rs
··· 1 1 #![warn(clippy::unwrap_used)] 2 + use crate::gate::{get_gate, post_gate}; 2 3 use crate::oauth_provider::sign_in; 3 - use crate::xrpc::com_atproto_server::{create_session, get_session, update_email}; 4 - use axum::body::Body; 5 - use axum::handler::Handler; 6 - use axum::http::{Method, header}; 7 - use axum::middleware as ax_middleware; 8 - use axum::routing::post; 9 - use axum::{Router, routing::get}; 4 + use crate::xrpc::com_atproto_server::{ 5 + create_account, create_session, describe_server, get_session, update_email, 6 + }; 7 + use anyhow::{Result, Context}; 8 + use axum::{ 9 + Router, 10 + body::Body, 11 + handler::Handler, 12 + http::{Method, header}, 13 + middleware as ax_middleware, 14 + routing::get, 15 + routing::post, 16 + }; 10 17 use axum_template::engine::Engine; 11 18 use handlebars::Handlebars; 12 - use hyper_util::client::legacy::connect::HttpConnector; 13 - use hyper_util::rt::TokioExecutor; 14 - use lettre::{AsyncSmtpTransport, Tokio1Executor}; 19 + use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; 20 + use jacquard_common::types::did::Did; 21 + use jacquard_identity::{PublicResolver, resolver::PlcSource}; 22 + use lettre::{AsyncTransport, AsyncSmtpTransport, AsyncSendmailTransport, Message, Tokio1Executor}; 23 + use rand::Rng; 15 24 use rust_embed::RustEmbed; 16 25 use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode}; 17 26 use sqlx::{SqlitePool, sqlite::SqlitePoolOptions}; 18 27 use std::path::Path; 28 + use std::sync::Arc; 19 29 use std::time::Duration; 20 30 use std::{env, net::SocketAddr}; 21 - use tower_governor::GovernorLayer; 22 - use tower_governor::governor::GovernorConfigBuilder; 23 - use tower_http::compression::CompressionLayer; 24 - use tower_http::cors::{Any, CorsLayer}; 31 + use tower_governor::{ 32 + GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor, 33 + }; 34 + use tower_http::{ 35 + compression::CompressionLayer, 36 + cors::{Any, CorsLayer}, 37 + }; 25 38 use tracing::log; 26 39 use tracing_subscriber::{EnvFilter, fmt, prelude::*}; 40 + use url::Url; 27 41 42 + mod gate; 28 43 pub mod helpers; 29 44 mod middleware; 30 45 mod oauth_provider; ··· 37 52 #[include = "*.hbs"] 38 53 struct EmailTemplates; 39 54 55 + #[derive(RustEmbed)] 56 + #[folder = "html_templates"] 57 + #[include = "*.hbs"] 58 + struct HtmlTemplates; 59 + 60 + /// Mostly the env variables that are used in the app 61 + #[derive(Clone, Debug)] 62 + pub struct AppConfig { 63 + pds_base_url: String, 64 + mailer_from: String, 65 + email_subject: String, 66 + allow_only_migrations: bool, 67 + use_captcha: bool, 68 + //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 69 + //that need to capture this redirect url for creating an account 70 + default_successful_redirect_url: String, 71 + pds_service_did: Did<'static>, 72 + gate_jwe_key: Vec<u8>, 73 + captcha_success_redirects: Vec<String>, 74 + } 75 + 76 + impl AppConfig { 77 + pub fn new() -> Self { 78 + let pds_base_url = 79 + env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string()); 80 + let mailer_from = env::var("PDS_EMAIL_FROM_ADDRESS") 81 + .expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file"); 82 + //Hack not my favorite, but it does work 83 + let allow_only_migrations = env::var("GATEKEEPER_ALLOW_ONLY_MIGRATIONS") 84 + .map(|val| val.parse::<bool>().unwrap_or(false)) 85 + .unwrap_or(false); 86 + 87 + let use_captcha = env::var("GATEKEEPER_CREATE_ACCOUNT_CAPTCHA") 88 + .map(|val| val.parse::<bool>().unwrap_or(false)) 89 + .unwrap_or(false); 90 + 91 + // PDS_SERVICE_DID is the did:web if set, if not it's PDS_HOSTNAME 92 + let pds_service_did = 93 + env::var("PDS_SERVICE_DID").unwrap_or_else(|_| match env::var("PDS_HOSTNAME") { 94 + Ok(pds_hostname) => format!("did:web:{}", pds_hostname), 95 + Err(_) => { 96 + panic!("PDS_HOSTNAME or PDS_SERVICE_DID must be set in your pds.env file") 97 + } 98 + }); 99 + 100 + let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT") 101 + .unwrap_or("Sign in to Bluesky".to_string()); 102 + 103 + // Load or generate JWE encryption key (32 bytes for AES-256) 104 + let gate_jwe_key = env::var("GATEKEEPER_JWE_KEY") 105 + .ok() 106 + .and_then(|key_hex| hex::decode(key_hex).ok()) 107 + .unwrap_or_else(|| { 108 + // Generate a random 32-byte key if not provided 109 + let key: Vec<u8> = (0..32).map(|_| rand::rng().random()).collect(); 110 + log::warn!("WARNING: No GATEKEEPER_JWE_KEY found in the environment. Generated random key (hex): {}", hex::encode(&key)); 111 + 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)."); 112 + key 113 + }); 114 + 115 + if gate_jwe_key.len() != 32 { 116 + panic!( 117 + "GATEKEEPER_JWE_KEY must be 32 bytes (64 hex characters) for AES-256 encryption" 118 + ); 119 + } 120 + 121 + let captcha_success_redirects = match env::var("GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS") { 122 + Ok(from_env) => from_env.split(",").map(|s| s.trim().to_string()).collect(), 123 + Err(_) => { 124 + vec![ 125 + String::from("https://bsky.app"), 126 + String::from("https://pdsmoover.com"), 127 + String::from("https://blacksky.community"), 128 + String::from("https://tektite.cc"), 129 + ] 130 + } 131 + }; 132 + 133 + AppConfig { 134 + pds_base_url, 135 + mailer_from, 136 + email_subject, 137 + allow_only_migrations, 138 + use_captcha, 139 + default_successful_redirect_url: env::var("GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT") 140 + .unwrap_or("https://bsky.app".to_string()), 141 + pds_service_did: pds_service_did 142 + .parse() 143 + .expect("PDS_SERVICE_DID is not a valid did or could not infer from PDS_HOSTNAME"), 144 + gate_jwe_key, 145 + captcha_success_redirects, 146 + } 147 + } 148 + } 149 + 40 150 #[derive(Clone)] 41 151 pub struct AppState { 42 152 account_pool: SqlitePool, 43 153 pds_gatekeeper_pool: SqlitePool, 44 154 reverse_proxy_client: HyperUtilClient, 45 - pds_base_url: String, 46 - mailer: AsyncSmtpTransport<Tokio1Executor>, 47 - mailer_from: String, 155 + mailer: Arc<Mailer>, 48 156 template_engine: Engine<Handlebars<'static>>, 157 + resolver: Arc<PublicResolver>, 158 + app_config: AppConfig, 159 + } 160 + 161 + pub enum Mailer { 162 + Smtp(AsyncSmtpTransport<Tokio1Executor>), 163 + Sendmail(AsyncSendmailTransport<Tokio1Executor>), 164 + } 165 + 166 + impl Mailer { 167 + pub async fn send(&self, msg: Message) -> Result<()> { 168 + match self { 169 + Mailer::Smtp(m) => { 170 + m.send(msg).await.context("SMTP send failed")?; 171 + Ok(()) 172 + } 173 + Mailer::Sendmail(m) => { 174 + m.send(msg).await.context("sendmail send failed")?; 175 + Ok(()) 176 + } 177 + } 178 + } 179 + } 180 + 181 + fn build_mailer_from_env() -> Result<Mailer> { 182 + let raw = env::var("PDS_EMAIL_SMTP_URL") 183 + .context("PDS_EMAIL_SMTP_URL is not set in your pds.env file")?; 184 + 185 + let url = Url::parse(&raw).context("PDS_EMAIL_SMTP_URL is not a valid URL")?; 186 + 187 + let use_sendmail = url.scheme() == "sendmail" 188 + || url.query_pairs().any(|(k, v)| k == "sendmail" && v == "true"); 189 + 190 + if use_sendmail { 191 + Ok(Mailer::Sendmail(AsyncSendmailTransport::<Tokio1Executor>::new())) 192 + } else { 193 + Ok(Mailer::Smtp( 194 + AsyncSmtpTransport::<Tokio1Executor>::from_url(raw.as_str())? 195 + .build(), 196 + )) 197 + } 49 198 } 50 199 51 200 async fn root_handler() -> impl axum::response::IntoResponse { ··· 88 237 #[tokio::main] 89 238 async fn main() -> Result<(), Box<dyn std::error::Error>> { 90 239 setup_tracing(); 91 - //TODO may need to change where this reads from? Like an env variable for it's location? Or arg? 92 - dotenvy::from_path(Path::new("./pds.env"))?; 93 - let pds_root = env::var("PDS_DATA_DIRECTORY")?; 240 + let pds_env_location = 241 + env::var("PDS_ENV_LOCATION").unwrap_or_else(|_| "/pds/pds.env".to_string()); 242 + 243 + let result_of_finding_pds_env = dotenvy::from_path(Path::new(&pds_env_location)); 244 + if let Err(e) = result_of_finding_pds_env { 245 + log::error!( 246 + "Error loading pds.env file (ignore if you loaded your variables in the environment somehow else): {e}" 247 + ); 248 + } 249 + 250 + let pds_root = 251 + env::var("PDS_DATA_DIRECTORY").expect("PDS_DATA_DIRECTORY is not set in your pds.env file"); 94 252 let account_db_url = format!("{pds_root}/account.sqlite"); 95 253 96 254 let account_options = SqliteConnectOptions::new() ··· 125 283 .build(HttpConnector::new()); 126 284 127 285 //Emailer set up 128 - let smtp_url = 129 - env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file"); 130 - let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS") 131 - .expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file"); 132 - let mailer: AsyncSmtpTransport<Tokio1Executor> = 133 - AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build(); 286 + let mailer = Arc::new(build_mailer_from_env()?); 287 + 134 288 //Email templates setup 135 289 let mut hbs = Handlebars::new(); 136 290 ··· 144 298 let _ = hbs.register_embed_templates::<EmailTemplates>(); 145 299 } 146 300 147 - let pds_base_url = 148 - env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string()); 301 + let _ = hbs.register_embed_templates::<HtmlTemplates>(); 302 + 303 + //Reads the PLC source from the pds env's or defaults to ol faithful 304 + let plc_source_url = 305 + env::var("PDS_DID_PLC_URL").unwrap_or_else(|_| "https://plc.directory".to_string()); 306 + let plc_source = PlcSource::PlcDirectory { 307 + base: plc_source_url.parse().unwrap(), 308 + }; 309 + let mut resolver = PublicResolver::default(); 310 + resolver = resolver.with_plc_source(plc_source.clone()); 149 311 150 312 let state = AppState { 151 313 account_pool, 152 314 pds_gatekeeper_pool, 153 315 reverse_proxy_client: client, 154 - pds_base_url, 155 316 mailer, 156 - mailer_from: sent_from, 157 317 template_engine: Engine::from(hbs), 318 + resolver: Arc::new(resolver), 319 + app_config: AppConfig::new(), 158 320 }; 159 321 160 322 // Rate limiting 161 323 //Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds. 162 - let create_session_governor_conf = GovernorConfigBuilder::default() 324 + let captcha_governor_conf = GovernorConfigBuilder::default() 163 325 .per_second(60) 164 326 .burst_size(5) 327 + .key_extractor(SmartIpKeyExtractor) 165 328 .finish() 166 - .expect("failed to create governor config. this should not happen and is a bug"); 329 + .expect("failed to create governor config for create session. this should not happen and is a bug"); 167 330 168 331 // Create a second config with the same settings for the other endpoint 169 332 let sign_in_governor_conf = GovernorConfigBuilder::default() 170 333 .per_second(60) 171 334 .burst_size(5) 335 + .key_extractor(SmartIpKeyExtractor) 172 336 .finish() 173 - .expect("failed to create governor config. this should not happen and is a bug"); 337 + .expect( 338 + "failed to create governor config for sign in. this should not happen and is a bug", 339 + ); 340 + 341 + let create_account_limiter_time: Option<String> = 342 + env::var("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND").ok(); 343 + let create_account_limiter_burst: Option<String> = 344 + env::var("GATEKEEPER_CREATE_ACCOUNT_BURST").ok(); 345 + 346 + //Default should be 608 requests per 5 minutes, PDS is 300 per 500 so will never hit it ideally 347 + let mut create_account_governor_conf = GovernorConfigBuilder::default(); 348 + if create_account_limiter_time.is_some() { 349 + let time = create_account_limiter_time 350 + .expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND not set") 351 + .parse::<u64>() 352 + .expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND must be a valid integer"); 353 + create_account_governor_conf.per_second(time); 354 + } 355 + 356 + if create_account_limiter_burst.is_some() { 357 + let burst = create_account_limiter_burst 358 + .expect("GATEKEEPER_CREATE_ACCOUNT_BURST not set") 359 + .parse::<u32>() 360 + .expect("GATEKEEPER_CREATE_ACCOUNT_BURST must be a valid integer"); 361 + create_account_governor_conf.burst_size(burst); 362 + } 363 + 364 + let create_account_governor_conf = create_account_governor_conf 365 + .key_extractor(SmartIpKeyExtractor) 366 + .finish().expect( 367 + "failed to create governor config for create account. this should not happen and is a bug", 368 + ); 174 369 175 - let create_session_governor_limiter = create_session_governor_conf.limiter().clone(); 370 + let captcha_governor_limiter = captcha_governor_conf.limiter().clone(); 176 371 let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone(); 372 + let create_account_governor_limiter = create_account_governor_conf.limiter().clone(); 373 + 374 + let sign_in_governor_layer = GovernorLayer::new(sign_in_governor_conf); 375 + 177 376 let interval = Duration::from_secs(60); 178 377 // a separate background task to clean up 179 378 std::thread::spawn(move || { 180 379 loop { 181 380 std::thread::sleep(interval); 182 - create_session_governor_limiter.retain_recent(); 381 + captcha_governor_limiter.retain_recent(); 183 382 sign_in_governor_limiter.retain_recent(); 383 + create_account_governor_limiter.retain_recent(); 184 384 } 185 385 }); 186 386 ··· 189 389 .allow_methods([Method::GET, Method::OPTIONS, Method::POST]) 190 390 .allow_headers(Any); 191 391 192 - let app = Router::new() 392 + let mut app = Router::new() 193 393 .route("/", get(root_handler)) 394 + .route("/xrpc/com.atproto.server.getSession", get(get_session)) 194 395 .route( 195 - "/xrpc/com.atproto.server.getSession", 196 - get(get_session).layer(ax_middleware::from_fn(middleware::extract_did)), 396 + "/xrpc/com.atproto.server.describeServer", 397 + get(describe_server), 197 398 ) 198 399 .route( 199 400 "/xrpc/com.atproto.server.updateEmail", ··· 201 402 ) 202 403 .route( 203 404 "/@atproto/oauth-provider/~api/sign-in", 204 - post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)), 405 + post(sign_in).layer(sign_in_governor_layer.clone()), 205 406 ) 206 407 .route( 207 408 "/xrpc/com.atproto.server.createSession", 208 - post(create_session.layer(GovernorLayer::new(create_session_governor_conf))), 409 + post(create_session.layer(sign_in_governor_layer)), 209 410 ) 411 + .route( 412 + "/xrpc/com.atproto.server.createAccount", 413 + post(create_account).layer(GovernorLayer::new(create_account_governor_conf)), 414 + ); 415 + 416 + if state.app_config.use_captcha { 417 + app = app.route( 418 + "/gate/signup", 419 + get(get_gate).post(post_gate.layer(GovernorLayer::new(captcha_governor_conf))), 420 + ); 421 + } 422 + 423 + let app = app 210 424 .layer(CompressionLayer::new()) 211 425 .layer(cors) 212 426 .with_state(state);
+73 -39
src/middleware.rs
··· 12 12 #[derive(Clone, Debug)] 13 13 pub struct Did(pub Option<String>); 14 14 15 + #[derive(Clone, Copy, Debug, PartialEq, Eq)] 16 + pub enum AuthScheme { 17 + Bearer, 18 + DPoP, 19 + } 20 + 15 21 #[derive(Serialize, Deserialize)] 16 22 pub struct TokenClaims { 17 23 pub sub: String, 18 24 } 19 25 20 26 pub async fn extract_did(mut req: Request, next: Next) -> impl IntoResponse { 21 - let token = extract_bearer(req.headers()); 27 + let auth = extract_auth(req.headers()); 22 28 23 - match token { 24 - Ok(token) => { 25 - match token { 29 + match auth { 30 + Ok(auth_opt) => { 31 + match auth_opt { 26 32 None => json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "") 27 33 .expect("Error creating an error response"), 28 - Some(token) => { 29 - let token = UntrustedToken::new(&token); 30 - if token.is_err() { 31 - return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "") 32 - .expect("Error creating an error response"); 33 - } 34 - let parsed_token = token.expect("Already checked for error"); 35 - let claims: Result<Claims<TokenClaims>, ValidationError> = 36 - parsed_token.deserialize_claims_unchecked(); 37 - if claims.is_err() { 38 - return json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "") 39 - .expect("Error creating an error response"); 40 - } 34 + Some((scheme, token_str)) => { 35 + // For Bearer, validate JWT and extract DID from `sub`. 36 + // For DPoP, we currently only pass through and do not validate here; insert None DID. 37 + match scheme { 38 + AuthScheme::Bearer => { 39 + let token = UntrustedToken::new(&token_str); 40 + if token.is_err() { 41 + return json_error_response( 42 + StatusCode::BAD_REQUEST, 43 + "TokenRequired", 44 + "", 45 + ) 46 + .expect("Error creating an error response"); 47 + } 48 + let parsed_token = token.expect("Already checked for error"); 49 + let claims: Result<Claims<TokenClaims>, ValidationError> = 50 + parsed_token.deserialize_claims_unchecked(); 51 + if claims.is_err() { 52 + return json_error_response( 53 + StatusCode::BAD_REQUEST, 54 + "TokenRequired", 55 + "", 56 + ) 57 + .expect("Error creating an error response"); 58 + } 41 59 42 - let key = Hs256Key::new( 43 - env::var("PDS_JWT_SECRET").expect("PDS_JWT_SECRET not set in the pds.env"), 44 - ); 45 - let token: Result<Token<TokenClaims>, ValidationError> = 46 - Hs256.validator(&key).validate(&parsed_token); 47 - if token.is_err() { 48 - return json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "") 49 - .expect("Error creating an error response"); 60 + let key = Hs256Key::new( 61 + env::var("PDS_JWT_SECRET") 62 + .expect("PDS_JWT_SECRET not set in the pds.env"), 63 + ); 64 + let token: Result<Token<TokenClaims>, ValidationError> = 65 + Hs256.validator(&key).validate(&parsed_token); 66 + if token.is_err() { 67 + return json_error_response( 68 + StatusCode::BAD_REQUEST, 69 + "InvalidToken", 70 + "", 71 + ) 72 + .expect("Error creating an error response"); 73 + } 74 + let token = token.expect("Already checked for error,"); 75 + req.extensions_mut() 76 + .insert(Did(Some(token.claims().custom.sub.clone()))); 77 + } 78 + AuthScheme::DPoP => { 79 + //Not going to worry about oauth email update for now, just always forward to the PDS 80 + req.extensions_mut().insert(Did(None)); 81 + } 50 82 } 51 - let token = token.expect("Already checked for error,"); 52 - //Not going to worry about expiration since it still goes to the PDS 53 - req.extensions_mut() 54 - .insert(Did(Some(token.claims().custom.sub.clone()))); 83 + 55 84 next.run(req).await 56 85 } 57 86 } ··· 64 93 } 65 94 } 66 95 67 - fn extract_bearer(headers: &HeaderMap) -> Result<Option<String>, String> { 96 + fn extract_auth(headers: &HeaderMap) -> Result<Option<(AuthScheme, String)>, String> { 68 97 match headers.get(axum::http::header::AUTHORIZATION) { 69 98 None => Ok(None), 70 - Some(hv) => match hv.to_str() { 71 - Err(_) => Err("Authorization header is not valid".into()), 72 - Ok(s) => { 73 - // Accept forms like: "Bearer <token>" (case-sensitive for the scheme here) 74 - let mut parts = s.splitn(2, ' '); 75 - match (parts.next(), parts.next()) { 76 - (Some("Bearer"), Some(tok)) if !tok.is_empty() => Ok(Some(tok.to_string())), 77 - _ => Err("Authorization header must be in format 'Bearer <token>'".into()), 99 + Some(hv) => { 100 + match hv.to_str() { 101 + Err(_) => Err("Authorization header is not valid".into()), 102 + Ok(s) => { 103 + // Accept forms like: "Bearer <token>" or "DPoP <token>" (case-sensitive for the scheme here) 104 + let mut parts = s.splitn(2, ' '); 105 + match (parts.next(), parts.next()) { 106 + (Some("Bearer"), Some(tok)) if !tok.is_empty() => 107 + Ok(Some((AuthScheme::Bearer, tok.to_string()))), 108 + (Some("DPoP"), Some(tok)) if !tok.is_empty() => 109 + Ok(Some((AuthScheme::DPoP, tok.to_string()))), 110 + _ => Err("Authorization header must be in format 'Bearer <token>' or 'DPoP <token>'".into()), 111 + } 78 112 } 79 113 } 80 - }, 114 + } 81 115 } 82 116 }
+4 -6
src/oauth_provider.rs
··· 13 13 pub struct SignInRequest { 14 14 pub username: String, 15 15 pub password: String, 16 - pub remember: bool, 16 + #[serde(skip_serializing_if = "Option::is_none")] 17 + pub remember: Option<bool>, 17 18 pub locale: String, 18 19 #[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")] 19 20 pub email_otp: Option<String>, ··· 36 37 "Invalid identifier or password", 37 38 ), 38 39 AuthResult::TwoFactorRequired(masked_email) => { 39 - // Email sending step can be handled here if needed in the future. 40 - 41 - // {"error":"second_authentication_factor_required","error_description":"emailOtp authentication factor required (hint: 2***0@p***m)","type":"emailOtp","hint":"2***0@p***m"} 42 40 let body_str = match serde_json::to_string(&serde_json::json!({ 43 41 "error": "second_authentication_factor_required", 44 42 "error_description": format!("emailOtp authentication factor required (hint: {})", masked_email), ··· 59 57 //No 2FA or already passed 60 58 let uri = format!( 61 59 "{}{}", 62 - state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in" 60 + state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in" 63 61 ); 64 62 65 63 let mut req = axum::http::Request::post(uri); ··· 97 95 }, 98 96 Err(err) => { 99 97 log::error!( 100 - "Error during pre-auth check. This happens on the create_session endpoint when trying to decide if the user has access:\n {err}" 98 + "Error during pre-auth check. This happens on the oauth signin endpoint when trying to decide if the user has access:\n {err}" 101 99 ); 102 100 oauth_json_error_response( 103 101 StatusCode::BAD_REQUEST,
+394 -53
src/xrpc/com_atproto_server.rs
··· 1 1 use crate::AppState; 2 2 use crate::helpers::{ 3 - AuthResult, ProxiedResult, TokenCheckError, json_error_response, preauth_check, proxy_get_json, 3 + AuthResult, ProxiedResult, TokenCheckError, VerifyServiceAuthError, json_error_response, 4 + preauth_check, proxy_get_json, verify_gate_token, verify_service_auth, 4 5 }; 5 6 use crate::middleware::Did; 6 - use axum::body::Body; 7 + use axum::body::{Body, to_bytes}; 7 8 use axum::extract::State; 8 - use axum::http::{HeaderMap, StatusCode}; 9 + use axum::http::{HeaderMap, StatusCode, header}; 9 10 use axum::response::{IntoResponse, Response}; 10 11 use axum::{Extension, Json, debug_handler, extract, extract::Request}; 12 + use chrono::{Duration, Utc}; 13 + use jacquard_common::types::did::Did as JacquardDid; 11 14 use serde::{Deserialize, Serialize}; 12 15 use serde_json; 13 16 use tracing::log; ··· 61 64 allow_takendown: Option<bool>, 62 65 } 63 66 67 + #[derive(Deserialize, Serialize, Debug)] 68 + #[serde(rename_all = "camelCase")] 69 + pub struct CreateAccountRequest { 70 + handle: String, 71 + #[serde(skip_serializing_if = "Option::is_none")] 72 + email: Option<String>, 73 + #[serde(skip_serializing_if = "Option::is_none")] 74 + password: Option<String>, 75 + #[serde(skip_serializing_if = "Option::is_none")] 76 + did: Option<String>, 77 + #[serde(skip_serializing_if = "Option::is_none")] 78 + invite_code: Option<String>, 79 + #[serde(skip_serializing_if = "Option::is_none")] 80 + verification_code: Option<String>, 81 + #[serde(skip_serializing_if = "Option::is_none")] 82 + plc_op: Option<serde_json::Value>, 83 + } 84 + 85 + #[derive(Deserialize, Serialize, Debug, Clone)] 86 + #[serde(rename_all = "camelCase")] 87 + pub struct DescribeServerContact { 88 + #[serde(skip_serializing_if = "Option::is_none")] 89 + email: Option<String>, 90 + } 91 + 92 + #[derive(Deserialize, Serialize, Debug, Clone)] 93 + #[serde(rename_all = "camelCase")] 94 + pub struct DescribeServerLinks { 95 + #[serde(skip_serializing_if = "Option::is_none")] 96 + privacy_policy: Option<String>, 97 + #[serde(skip_serializing_if = "Option::is_none")] 98 + terms_of_service: Option<String>, 99 + } 100 + 101 + #[derive(Deserialize, Serialize, Debug, Clone)] 102 + #[serde(rename_all = "camelCase")] 103 + pub struct DescribeServerResponse { 104 + #[serde(skip_serializing_if = "Option::is_none")] 105 + invite_code_required: Option<bool>, 106 + #[serde(skip_serializing_if = "Option::is_none")] 107 + phone_verification_required: Option<bool>, 108 + #[serde(skip_serializing_if = "Option::is_none")] 109 + available_user_domains: Option<Vec<String>>, 110 + #[serde(skip_serializing_if = "Option::is_none")] 111 + links: Option<DescribeServerLinks>, 112 + #[serde(skip_serializing_if = "Option::is_none")] 113 + contact: Option<DescribeServerContact>, 114 + #[serde(skip_serializing_if = "Option::is_none")] 115 + did: Option<String>, 116 + } 117 + 64 118 pub async fn create_session( 65 119 State(state): State<AppState>, 66 120 headers: HeaderMap, ··· 87 141 ) 88 142 } 89 143 AuthResult::ProxyThrough => { 90 - log::info!("Proxying through"); 91 144 //No 2FA or already passed 92 145 let uri = format!( 93 146 "{}{}", 94 - state.pds_base_url, "/xrpc/com.atproto.server.createSession" 147 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession" 95 148 ); 96 149 97 150 let mut req = axum::http::Request::post(uri); ··· 148 201 //If email auth is set it is to either turn on or off 2fa 149 202 let email_auth_update = payload.email_auth_factor.unwrap_or(false); 150 203 151 - // Email update asked for 152 - if email_auth_update { 153 - let email = payload.email.clone(); 154 - let email_confirmed = sqlx::query_as::<_, (String,)>( 155 - "SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?", 156 - ) 157 - .bind(&email) 158 - .fetch_optional(&state.account_pool) 159 - .await 160 - .map_err(|_| StatusCode::BAD_REQUEST)?; 161 - 162 - //Since the email is already confirmed we can enable 2fa 163 - return match email_confirmed { 164 - None => Err(StatusCode::BAD_REQUEST), 165 - Some(did_row) => { 166 - let _ = sqlx::query( 167 - "INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1", 168 - ) 169 - .bind(&did_row.0) 170 - .execute(&state.pds_gatekeeper_pool) 171 - .await 172 - .map_err(|_| StatusCode::BAD_REQUEST)?; 173 - 174 - Ok(StatusCode::OK.into_response()) 175 - } 176 - }; 177 - } 204 + //This means the middleware successfully extracted a did from the request, if not it just needs to be forward to the PDS 205 + //This is also empty if it is an oauth request, which is not supported by gatekeeper turning on 2fa since the dpop stuff needs to be implemented 206 + let did_is_not_empty = did.0.is_some(); 178 207 179 - // User wants auth turned off 180 - if !email_auth_update && !email_auth_not_set { 181 - //User wants auth turned off and has a token 182 - if let Some(token) = &payload.token { 183 - let token_found = sqlx::query_as::<_, (String,)>( 184 - "SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'", 208 + if did_is_not_empty { 209 + // Email update asked for 210 + if email_auth_update { 211 + let email = payload.email.clone(); 212 + let email_confirmed = match sqlx::query_as::<_, (String,)>( 213 + "SELECT did FROM account WHERE emailConfirmedAt IS NOT NULL AND email = ?", 185 214 ) 186 - .bind(token) 187 - .bind(&did.0) 215 + .bind(&email) 188 216 .fetch_optional(&state.account_pool) 189 217 .await 190 - .map_err(|_| StatusCode::BAD_REQUEST)?; 218 + { 219 + Ok(row) => row, 220 + Err(err) => { 221 + log::error!("Error checking if email is confirmed: {err}"); 222 + return Err(StatusCode::BAD_REQUEST); 223 + } 224 + }; 191 225 192 - if token_found.is_some() { 193 - let _ = sqlx::query( 194 - "INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0", 226 + //Since the email is already confirmed we can enable 2fa 227 + return match email_confirmed { 228 + None => Err(StatusCode::BAD_REQUEST), 229 + Some(did_row) => { 230 + let _ = sqlx::query( 231 + "INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1", 232 + ) 233 + .bind(&did_row.0) 234 + .execute(&state.pds_gatekeeper_pool) 235 + .await 236 + .map_err(|_| StatusCode::BAD_REQUEST)?; 237 + 238 + Ok(StatusCode::OK.into_response()) 239 + } 240 + }; 241 + } 242 + 243 + // User wants auth turned off 244 + if !email_auth_update && !email_auth_not_set { 245 + //User wants auth turned off and has a token 246 + if let Some(token) = &payload.token { 247 + let token_found = match sqlx::query_as::<_, (String,)>( 248 + "SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'", 195 249 ) 196 - .bind(&did.0) 197 - .execute(&state.pds_gatekeeper_pool) 198 - .await 199 - .map_err(|_| StatusCode::BAD_REQUEST)?; 250 + .bind(token) 251 + .bind(&did.0) 252 + .fetch_optional(&state.account_pool) 253 + .await{ 254 + Ok(token) => token, 255 + Err(err) => { 256 + log::error!("Error checking if token is valid: {err}"); 257 + return Err(StatusCode::BAD_REQUEST); 258 + } 259 + }; 200 260 201 - return Ok(StatusCode::OK.into_response()); 202 - } else { 203 - return Err(StatusCode::BAD_REQUEST); 261 + return if token_found.is_some() { 262 + //TODO I think there may be a bug here and need to do some retry logic 263 + // First try was erroring, seconds was allowing 264 + match sqlx::query( 265 + "INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0", 266 + ) 267 + .bind(&did.0) 268 + .execute(&state.pds_gatekeeper_pool) 269 + .await { 270 + Ok(_) => {} 271 + Err(err) => { 272 + log::error!("Error updating email auth: {err}"); 273 + return Err(StatusCode::BAD_REQUEST); 274 + } 275 + } 276 + 277 + Ok(StatusCode::OK.into_response()) 278 + } else { 279 + Err(StatusCode::BAD_REQUEST) 280 + }; 204 281 } 205 282 } 206 283 } 207 - 208 284 // Updating the actual email address by sending it on to the PDS 209 285 let uri = format!( 210 286 "{}{}", 211 - state.pds_base_url, "/xrpc/com.atproto.server.updateEmail" 287 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail" 212 288 ); 213 289 let mut req = axum::http::Request::post(uri); 214 290 if let Some(req_headers) = req.headers_mut() { ··· 260 336 ProxiedResult::Passthrough(resp) => Ok(resp), 261 337 } 262 338 } 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 + 425 + pub async fn create_account( 426 + State(state): State<AppState>, 427 + req: Request, 428 + ) -> Result<Response<Body>, StatusCode> { 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 + }; 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 581 + let uri = format!( 582 + "{}{}", 583 + state.app_config.pds_base_url, "/xrpc/com.atproto.server.createAccount" 584 + ); 585 + 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)?; 594 + 595 + let proxied = state 596 + .reverse_proxy_client 597 + .request(new_req) 598 + .await 599 + .map_err(|_| StatusCode::BAD_REQUEST)? 600 + .into_response(); 601 + 602 + Ok(proxied) 603 + }