+1402
-88
Cargo.lock
+1402
-88
Cargo.lock
···
3
3
version = 4
4
4
5
5
[[package]]
6
+
name = "abnf"
7
+
version = "0.13.0"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "087113bd50d9adce24850eed5d0476c7d199d532fce8fab5173650331e09033a"
10
+
dependencies = [
11
+
"abnf-core",
12
+
"nom 7.1.3",
13
+
]
14
+
15
+
[[package]]
16
+
name = "abnf-core"
17
+
version = "0.5.0"
18
+
source = "registry+https://github.com/rust-lang/crates.io-index"
19
+
checksum = "c44e09c43ae1c368fb91a03a566472d0087c26cf7e1b9e8e289c14ede681dd7d"
20
+
dependencies = [
21
+
"nom 7.1.3",
22
+
]
23
+
24
+
[[package]]
6
25
name = "addr2line"
7
26
version = "0.24.2"
8
27
source = "registry+https://github.com/rust-lang/crates.io-index"
···
39
58
]
40
59
41
60
[[package]]
61
+
name = "aliasable"
62
+
version = "0.1.3"
63
+
source = "registry+https://github.com/rust-lang/crates.io-index"
64
+
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
65
+
66
+
[[package]]
42
67
name = "allocator-api2"
43
68
version = "0.2.21"
44
69
source = "registry+https://github.com/rust-lang/crates.io-index"
45
70
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
46
71
47
72
[[package]]
48
-
name = "android-tzdata"
49
-
version = "0.1.1"
50
-
source = "registry+https://github.com/rust-lang/crates.io-index"
51
-
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
52
-
53
-
[[package]]
54
73
name = "android_system_properties"
55
74
version = "0.1.5"
56
75
source = "registry+https://github.com/rust-lang/crates.io-index"
···
71
90
source = "registry+https://github.com/rust-lang/crates.io-index"
72
91
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
73
92
dependencies = [
93
+
"flate2",
74
94
"futures-core",
75
95
"memchr",
76
96
"pin-project-lite",
···
87
107
dependencies = [
88
108
"proc-macro2",
89
109
"quote",
90
-
"syn",
110
+
"syn 2.0.105",
91
111
]
92
112
93
113
[[package]]
···
198
218
dependencies = [
199
219
"proc-macro2",
200
220
"quote",
201
-
"syn",
221
+
"syn 2.0.105",
202
222
]
203
223
204
224
[[package]]
···
229
249
]
230
250
231
251
[[package]]
252
+
name = "base-x"
253
+
version = "0.2.11"
254
+
source = "registry+https://github.com/rust-lang/crates.io-index"
255
+
checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270"
256
+
257
+
[[package]]
258
+
name = "base16ct"
259
+
version = "0.2.0"
260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
261
+
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
262
+
263
+
[[package]]
264
+
name = "base256emoji"
265
+
version = "1.0.2"
266
+
source = "registry+https://github.com/rust-lang/crates.io-index"
267
+
checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c"
268
+
dependencies = [
269
+
"const-str",
270
+
"match-lookup",
271
+
]
272
+
273
+
[[package]]
232
274
name = "base64"
233
275
version = "0.22.1"
234
276
source = "registry+https://github.com/rust-lang/crates.io-index"
···
257
299
"proc-macro2",
258
300
"quote",
259
301
"regex",
260
-
"rustc-hash",
302
+
"rustc-hash 1.1.0",
261
303
"shlex",
262
-
"syn",
304
+
"syn 2.0.105",
263
305
"which",
264
306
]
265
307
···
282
324
]
283
325
284
326
[[package]]
327
+
name = "bon"
328
+
version = "3.8.1"
329
+
source = "registry+https://github.com/rust-lang/crates.io-index"
330
+
checksum = "ebeb9aaf9329dff6ceb65c689ca3db33dbf15f324909c60e4e5eef5701ce31b1"
331
+
dependencies = [
332
+
"bon-macros",
333
+
"rustversion",
334
+
]
335
+
336
+
[[package]]
337
+
name = "bon-macros"
338
+
version = "3.8.1"
339
+
source = "registry+https://github.com/rust-lang/crates.io-index"
340
+
checksum = "77e9d642a7e3a318e37c2c9427b5a6a48aa1ad55dcd986f3034ab2239045a645"
341
+
dependencies = [
342
+
"darling 0.21.3",
343
+
"ident_case",
344
+
"prettyplease",
345
+
"proc-macro2",
346
+
"quote",
347
+
"rustversion",
348
+
"syn 2.0.105",
349
+
]
350
+
351
+
[[package]]
352
+
name = "borsh"
353
+
version = "1.6.0"
354
+
source = "registry+https://github.com/rust-lang/crates.io-index"
355
+
checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f"
356
+
dependencies = [
357
+
"cfg_aliases",
358
+
]
359
+
360
+
[[package]]
285
361
name = "bstr"
286
362
version = "1.12.0"
287
363
source = "registry+https://github.com/rust-lang/crates.io-index"
···
292
368
]
293
369
294
370
[[package]]
371
+
name = "btree-range-map"
372
+
version = "0.7.2"
373
+
source = "registry+https://github.com/rust-lang/crates.io-index"
374
+
checksum = "1be5c9672446d3800bcbcaabaeba121fe22f1fb25700c4562b22faf76d377c33"
375
+
dependencies = [
376
+
"btree-slab",
377
+
"cc-traits",
378
+
"range-traits",
379
+
"serde",
380
+
"slab",
381
+
]
382
+
383
+
[[package]]
384
+
name = "btree-slab"
385
+
version = "0.6.1"
386
+
source = "registry+https://github.com/rust-lang/crates.io-index"
387
+
checksum = "7a2b56d3029f075c4fa892428a098425b86cef5c89ae54073137ece416aef13c"
388
+
dependencies = [
389
+
"cc-traits",
390
+
"slab",
391
+
"smallvec",
392
+
]
393
+
394
+
[[package]]
295
395
name = "bumpalo"
296
396
version = "3.19.0"
297
397
source = "registry+https://github.com/rust-lang/crates.io-index"
···
308
408
version = "1.10.1"
309
409
source = "registry+https://github.com/rust-lang/crates.io-index"
310
410
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
411
+
dependencies = [
412
+
"serde",
413
+
]
414
+
415
+
[[package]]
416
+
name = "cbor4ii"
417
+
version = "0.2.14"
418
+
source = "registry+https://github.com/rust-lang/crates.io-index"
419
+
checksum = "b544cf8c89359205f4f990d0e6f3828db42df85b5dac95d09157a250eb0749c4"
420
+
dependencies = [
421
+
"serde",
422
+
]
311
423
312
424
[[package]]
313
425
name = "cc"
···
318
430
"jobserver",
319
431
"libc",
320
432
"shlex",
433
+
]
434
+
435
+
[[package]]
436
+
name = "cc-traits"
437
+
version = "2.0.0"
438
+
source = "registry+https://github.com/rust-lang/crates.io-index"
439
+
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
440
+
dependencies = [
441
+
"slab",
321
442
]
322
443
323
444
[[package]]
···
336
457
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
337
458
338
459
[[package]]
460
+
name = "cfg_aliases"
461
+
version = "0.2.1"
462
+
source = "registry+https://github.com/rust-lang/crates.io-index"
463
+
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
464
+
465
+
[[package]]
339
466
name = "chrono"
340
-
version = "0.4.41"
467
+
version = "0.4.42"
341
468
source = "registry+https://github.com/rust-lang/crates.io-index"
342
-
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
469
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
343
470
dependencies = [
344
-
"android-tzdata",
345
471
"iana-time-zone",
346
472
"js-sys",
347
473
"num-traits",
474
+
"serde",
348
475
"wasm-bindgen",
349
-
"windows-link",
476
+
"windows-link 0.2.1",
350
477
]
351
478
352
479
[[package]]
···
387
514
]
388
515
389
516
[[package]]
517
+
name = "cid"
518
+
version = "0.11.1"
519
+
source = "registry+https://github.com/rust-lang/crates.io-index"
520
+
checksum = "3147d8272e8fa0ccd29ce51194dd98f79ddfb8191ba9e3409884e751798acf3a"
521
+
dependencies = [
522
+
"core2",
523
+
"multibase",
524
+
"multihash",
525
+
"serde",
526
+
"serde_bytes",
527
+
"unsigned-varint",
528
+
]
529
+
530
+
[[package]]
390
531
name = "cipher"
391
532
version = "0.4.4"
392
533
source = "registry+https://github.com/rust-lang/crates.io-index"
···
432
573
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
433
574
434
575
[[package]]
576
+
name = "const-str"
577
+
version = "0.4.3"
578
+
source = "registry+https://github.com/rust-lang/crates.io-index"
579
+
checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3"
580
+
581
+
[[package]]
582
+
name = "core-foundation"
583
+
version = "0.9.4"
584
+
source = "registry+https://github.com/rust-lang/crates.io-index"
585
+
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
586
+
dependencies = [
587
+
"core-foundation-sys",
588
+
"libc",
589
+
]
590
+
591
+
[[package]]
435
592
name = "core-foundation-sys"
436
593
version = "0.8.7"
437
594
source = "registry+https://github.com/rust-lang/crates.io-index"
438
595
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
439
596
440
597
[[package]]
598
+
name = "core2"
599
+
version = "0.4.0"
600
+
source = "registry+https://github.com/rust-lang/crates.io-index"
601
+
checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
602
+
dependencies = [
603
+
"memchr",
604
+
]
605
+
606
+
[[package]]
441
607
name = "cpufeatures"
442
608
version = "0.2.17"
443
609
source = "registry+https://github.com/rust-lang/crates.io-index"
···
462
628
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
463
629
464
630
[[package]]
631
+
name = "crc32fast"
632
+
version = "1.5.0"
633
+
source = "registry+https://github.com/rust-lang/crates.io-index"
634
+
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
635
+
dependencies = [
636
+
"cfg-if",
637
+
]
638
+
639
+
[[package]]
465
640
name = "crossbeam-queue"
466
641
version = "0.3.12"
467
642
source = "registry+https://github.com/rust-lang/crates.io-index"
···
483
658
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
484
659
485
660
[[package]]
661
+
name = "crypto-bigint"
662
+
version = "0.5.5"
663
+
source = "registry+https://github.com/rust-lang/crates.io-index"
664
+
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
665
+
dependencies = [
666
+
"generic-array",
667
+
"rand_core 0.6.4",
668
+
"subtle",
669
+
"zeroize",
670
+
]
671
+
672
+
[[package]]
486
673
name = "crypto-common"
487
674
version = "0.1.6"
488
675
source = "registry+https://github.com/rust-lang/crates.io-index"
···
498
685
source = "registry+https://github.com/rust-lang/crates.io-index"
499
686
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
500
687
dependencies = [
501
-
"darling_core",
502
-
"darling_macro",
688
+
"darling_core 0.20.11",
689
+
"darling_macro 0.20.11",
690
+
]
691
+
692
+
[[package]]
693
+
name = "darling"
694
+
version = "0.21.3"
695
+
source = "registry+https://github.com/rust-lang/crates.io-index"
696
+
checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
697
+
dependencies = [
698
+
"darling_core 0.21.3",
699
+
"darling_macro 0.21.3",
503
700
]
504
701
505
702
[[package]]
···
513
710
"proc-macro2",
514
711
"quote",
515
712
"strsim",
516
-
"syn",
713
+
"syn 2.0.105",
714
+
]
715
+
716
+
[[package]]
717
+
name = "darling_core"
718
+
version = "0.21.3"
719
+
source = "registry+https://github.com/rust-lang/crates.io-index"
720
+
checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
721
+
dependencies = [
722
+
"fnv",
723
+
"ident_case",
724
+
"proc-macro2",
725
+
"quote",
726
+
"strsim",
727
+
"syn 2.0.105",
517
728
]
518
729
519
730
[[package]]
···
522
733
source = "registry+https://github.com/rust-lang/crates.io-index"
523
734
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
524
735
dependencies = [
525
-
"darling_core",
736
+
"darling_core 0.20.11",
526
737
"quote",
527
-
"syn",
738
+
"syn 2.0.105",
739
+
]
740
+
741
+
[[package]]
742
+
name = "darling_macro"
743
+
version = "0.21.3"
744
+
source = "registry+https://github.com/rust-lang/crates.io-index"
745
+
checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
746
+
dependencies = [
747
+
"darling_core 0.21.3",
748
+
"quote",
749
+
"syn 2.0.105",
528
750
]
529
751
530
752
[[package]]
···
542
764
]
543
765
544
766
[[package]]
767
+
name = "data-encoding"
768
+
version = "2.9.0"
769
+
source = "registry+https://github.com/rust-lang/crates.io-index"
770
+
checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
771
+
772
+
[[package]]
773
+
name = "data-encoding-macro"
774
+
version = "0.1.18"
775
+
source = "registry+https://github.com/rust-lang/crates.io-index"
776
+
checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d"
777
+
dependencies = [
778
+
"data-encoding",
779
+
"data-encoding-macro-internal",
780
+
]
781
+
782
+
[[package]]
783
+
name = "data-encoding-macro-internal"
784
+
version = "0.1.16"
785
+
source = "registry+https://github.com/rust-lang/crates.io-index"
786
+
checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976"
787
+
dependencies = [
788
+
"data-encoding",
789
+
"syn 2.0.105",
790
+
]
791
+
792
+
[[package]]
545
793
name = "der"
546
794
version = "0.7.10"
547
795
source = "registry+https://github.com/rust-lang/crates.io-index"
···
553
801
]
554
802
555
803
[[package]]
804
+
name = "deranged"
805
+
version = "0.5.5"
806
+
source = "registry+https://github.com/rust-lang/crates.io-index"
807
+
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
808
+
dependencies = [
809
+
"powerfmt",
810
+
"serde_core",
811
+
]
812
+
813
+
[[package]]
556
814
name = "derive_builder"
557
815
version = "0.20.2"
558
816
source = "registry+https://github.com/rust-lang/crates.io-index"
···
567
825
source = "registry+https://github.com/rust-lang/crates.io-index"
568
826
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
569
827
dependencies = [
570
-
"darling",
828
+
"darling 0.20.11",
571
829
"proc-macro2",
572
830
"quote",
573
-
"syn",
831
+
"syn 2.0.105",
574
832
]
575
833
576
834
[[package]]
···
580
838
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
581
839
dependencies = [
582
840
"derive_builder_core",
583
-
"syn",
841
+
"syn 2.0.105",
584
842
]
585
843
586
844
[[package]]
···
603
861
dependencies = [
604
862
"proc-macro2",
605
863
"quote",
606
-
"syn",
864
+
"syn 2.0.105",
607
865
]
608
866
609
867
[[package]]
···
619
877
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
620
878
621
879
[[package]]
880
+
name = "dyn-clone"
881
+
version = "1.0.20"
882
+
source = "registry+https://github.com/rust-lang/crates.io-index"
883
+
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
884
+
885
+
[[package]]
886
+
name = "ecdsa"
887
+
version = "0.16.9"
888
+
source = "registry+https://github.com/rust-lang/crates.io-index"
889
+
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
890
+
dependencies = [
891
+
"der",
892
+
"digest",
893
+
"elliptic-curve",
894
+
"rfc6979",
895
+
"signature",
896
+
"spki",
897
+
]
898
+
899
+
[[package]]
622
900
name = "either"
623
901
version = "1.15.0"
624
902
source = "registry+https://github.com/rust-lang/crates.io-index"
···
628
906
]
629
907
630
908
[[package]]
909
+
name = "elliptic-curve"
910
+
version = "0.13.8"
911
+
source = "registry+https://github.com/rust-lang/crates.io-index"
912
+
checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
913
+
dependencies = [
914
+
"base16ct",
915
+
"crypto-bigint",
916
+
"digest",
917
+
"ff",
918
+
"generic-array",
919
+
"group",
920
+
"pem-rfc7468",
921
+
"pkcs8",
922
+
"rand_core 0.6.4",
923
+
"sec1",
924
+
"subtle",
925
+
"zeroize",
926
+
]
927
+
928
+
[[package]]
631
929
name = "email-encoding"
632
930
version = "0.4.1"
633
931
source = "registry+https://github.com/rust-lang/crates.io-index"
···
644
942
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
645
943
646
944
[[package]]
945
+
name = "encoding_rs"
946
+
version = "0.8.35"
947
+
source = "registry+https://github.com/rust-lang/crates.io-index"
948
+
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
949
+
dependencies = [
950
+
"cfg-if",
951
+
]
952
+
953
+
[[package]]
647
954
name = "equivalent"
648
955
version = "1.0.2"
649
956
source = "registry+https://github.com/rust-lang/crates.io-index"
···
656
963
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
657
964
dependencies = [
658
965
"libc",
659
-
"windows-sys 0.52.0",
966
+
"windows-sys 0.59.0",
660
967
]
661
968
662
969
[[package]]
···
688
995
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
689
996
690
997
[[package]]
998
+
name = "ff"
999
+
version = "0.13.1"
1000
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1001
+
checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393"
1002
+
dependencies = [
1003
+
"rand_core 0.6.4",
1004
+
"subtle",
1005
+
]
1006
+
1007
+
[[package]]
1008
+
name = "flate2"
1009
+
version = "1.1.5"
1010
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1011
+
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
1012
+
dependencies = [
1013
+
"crc32fast",
1014
+
"miniz_oxide",
1015
+
]
1016
+
1017
+
[[package]]
691
1018
name = "flume"
692
1019
version = "0.11.1"
693
1020
source = "registry+https://github.com/rust-lang/crates.io-index"
···
709
1036
version = "0.1.5"
710
1037
source = "registry+https://github.com/rust-lang/crates.io-index"
711
1038
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
1039
+
1040
+
[[package]]
1041
+
name = "foreign-types"
1042
+
version = "0.3.2"
1043
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1044
+
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
1045
+
dependencies = [
1046
+
"foreign-types-shared",
1047
+
]
1048
+
1049
+
[[package]]
1050
+
name = "foreign-types-shared"
1051
+
version = "0.1.1"
1052
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1053
+
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
712
1054
713
1055
[[package]]
714
1056
name = "form_urlencoded"
···
780
1122
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
781
1123
782
1124
[[package]]
1125
+
name = "futures-macro"
1126
+
version = "0.3.31"
1127
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1128
+
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
1129
+
dependencies = [
1130
+
"proc-macro2",
1131
+
"quote",
1132
+
"syn 2.0.105",
1133
+
]
1134
+
1135
+
[[package]]
783
1136
name = "futures-sink"
784
1137
version = "0.3.31"
785
1138
source = "registry+https://github.com/rust-lang/crates.io-index"
···
805
1158
dependencies = [
806
1159
"futures-core",
807
1160
"futures-io",
1161
+
"futures-macro",
808
1162
"futures-sink",
809
1163
"futures-task",
810
1164
"memchr",
···
821
1175
dependencies = [
822
1176
"typenum",
823
1177
"version_check",
1178
+
"zeroize",
824
1179
]
825
1180
826
1181
[[package]]
···
830
1185
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
831
1186
dependencies = [
832
1187
"cfg-if",
1188
+
"js-sys",
833
1189
"libc",
834
-
"wasi 0.11.1+wasi-snapshot-preview1",
1190
+
"wasi",
1191
+
"wasm-bindgen",
835
1192
]
836
1193
837
1194
[[package]]
838
1195
name = "getrandom"
839
-
version = "0.3.3"
1196
+
version = "0.3.4"
840
1197
source = "registry+https://github.com/rust-lang/crates.io-index"
841
-
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
1198
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
842
1199
dependencies = [
843
1200
"cfg-if",
844
1201
"js-sys",
845
1202
"libc",
846
1203
"r-efi",
847
-
"wasi 0.14.2+wasi-0.2.4",
1204
+
"wasip2",
848
1205
"wasm-bindgen",
849
1206
]
850
1207
···
869
1226
"aho-corasick",
870
1227
"bstr",
871
1228
"log",
872
-
"regex-automata 0.4.9",
1229
+
"regex-automata 0.4.13",
873
1230
"regex-syntax 0.8.5",
874
1231
]
875
1232
···
884
1241
"futures-sink",
885
1242
"futures-timer",
886
1243
"futures-util",
887
-
"getrandom 0.3.3",
1244
+
"getrandom 0.3.4",
888
1245
"hashbrown 0.15.5",
889
1246
"nonzero_ext",
890
1247
"parking_lot",
···
897
1254
]
898
1255
899
1256
[[package]]
1257
+
name = "group"
1258
+
version = "0.13.0"
1259
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1260
+
checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
1261
+
dependencies = [
1262
+
"ff",
1263
+
"rand_core 0.6.4",
1264
+
"subtle",
1265
+
]
1266
+
1267
+
[[package]]
900
1268
name = "h2"
901
1269
version = "0.4.12"
902
1270
source = "registry+https://github.com/rust-lang/crates.io-index"
···
908
1276
"futures-core",
909
1277
"futures-sink",
910
1278
"http",
911
-
"indexmap",
1279
+
"indexmap 2.10.0",
912
1280
"slab",
913
1281
"tokio",
914
1282
"tokio-util",
···
944
1312
945
1313
[[package]]
946
1314
name = "hashbrown"
1315
+
version = "0.12.3"
1316
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1317
+
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
1318
+
1319
+
[[package]]
1320
+
name = "hashbrown"
947
1321
version = "0.14.5"
948
1322
source = "registry+https://github.com/rust-lang/crates.io-index"
949
1323
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
···
974
1348
975
1349
[[package]]
976
1350
name = "heck"
1351
+
version = "0.4.1"
1352
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1353
+
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
1354
+
1355
+
[[package]]
1356
+
name = "heck"
977
1357
version = "0.5.0"
978
1358
source = "registry+https://github.com/rust-lang/crates.io-index"
979
1359
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
···
985
1365
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
986
1366
987
1367
[[package]]
1368
+
name = "hex_fmt"
1369
+
version = "0.3.0"
1370
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1371
+
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
1372
+
1373
+
[[package]]
988
1374
name = "hkdf"
989
1375
version = "0.12.4"
990
1376
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1009
1395
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
1010
1396
dependencies = [
1011
1397
"windows-sys 0.59.0",
1398
+
]
1399
+
1400
+
[[package]]
1401
+
name = "html-escape"
1402
+
version = "0.2.13"
1403
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1404
+
checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
1405
+
dependencies = [
1406
+
"utf8-width",
1012
1407
]
1013
1408
1014
1409
[[package]]
···
1079
1474
]
1080
1475
1081
1476
[[package]]
1477
+
name = "hyper-rustls"
1478
+
version = "0.27.7"
1479
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1480
+
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
1481
+
dependencies = [
1482
+
"http",
1483
+
"hyper",
1484
+
"hyper-util",
1485
+
"rustls",
1486
+
"rustls-pki-types",
1487
+
"tokio",
1488
+
"tokio-rustls",
1489
+
"tower-service",
1490
+
"webpki-roots 1.0.2",
1491
+
]
1492
+
1493
+
[[package]]
1082
1494
name = "hyper-timeout"
1083
1495
version = "0.5.2"
1084
1496
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1097
1509
source = "registry+https://github.com/rust-lang/crates.io-index"
1098
1510
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
1099
1511
dependencies = [
1512
+
"base64",
1100
1513
"bytes",
1101
1514
"futures-channel",
1102
1515
"futures-core",
···
1104
1517
"http",
1105
1518
"http-body",
1106
1519
"hyper",
1520
+
"ipnet",
1107
1521
"libc",
1522
+
"percent-encoding",
1108
1523
"pin-project-lite",
1109
1524
"socket2",
1525
+
"system-configuration",
1110
1526
"tokio",
1111
1527
"tower-service",
1112
1528
"tracing",
1529
+
"windows-registry",
1113
1530
]
1114
1531
1115
1532
[[package]]
···
1251
1668
1252
1669
[[package]]
1253
1670
name = "indexmap"
1671
+
version = "1.9.3"
1672
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1673
+
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
1674
+
dependencies = [
1675
+
"autocfg",
1676
+
"hashbrown 0.12.3",
1677
+
"serde",
1678
+
]
1679
+
1680
+
[[package]]
1681
+
name = "indexmap"
1254
1682
version = "2.10.0"
1255
1683
source = "registry+https://github.com/rust-lang/crates.io-index"
1256
1684
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
1257
1685
dependencies = [
1258
1686
"equivalent",
1259
1687
"hashbrown 0.15.5",
1688
+
"serde",
1689
+
]
1690
+
1691
+
[[package]]
1692
+
name = "indoc"
1693
+
version = "2.0.7"
1694
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1695
+
checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
1696
+
dependencies = [
1697
+
"rustversion",
1260
1698
]
1261
1699
1262
1700
[[package]]
···
1269
1707
]
1270
1708
1271
1709
[[package]]
1710
+
name = "inventory"
1711
+
version = "0.3.21"
1712
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1713
+
checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e"
1714
+
dependencies = [
1715
+
"rustversion",
1716
+
]
1717
+
1718
+
[[package]]
1272
1719
name = "io-uring"
1273
1720
version = "0.7.9"
1274
1721
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1280
1727
]
1281
1728
1282
1729
[[package]]
1730
+
name = "ipld-core"
1731
+
version = "0.4.2"
1732
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1733
+
checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db"
1734
+
dependencies = [
1735
+
"cid",
1736
+
"serde",
1737
+
"serde_bytes",
1738
+
]
1739
+
1740
+
[[package]]
1741
+
name = "ipnet"
1742
+
version = "2.11.0"
1743
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1744
+
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
1745
+
1746
+
[[package]]
1747
+
name = "iri-string"
1748
+
version = "0.7.9"
1749
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1750
+
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
1751
+
dependencies = [
1752
+
"memchr",
1753
+
"serde",
1754
+
]
1755
+
1756
+
[[package]]
1283
1757
name = "itertools"
1284
1758
version = "0.12.1"
1285
1759
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1295
1769
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
1296
1770
1297
1771
[[package]]
1772
+
name = "jacquard-api"
1773
+
version = "0.9.2"
1774
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1775
+
checksum = "bbbfd6e2b10fa1731f4d4e40c8f791956b0d4f804fb3efef891afec903f20597"
1776
+
dependencies = [
1777
+
"bon",
1778
+
"bytes",
1779
+
"jacquard-common",
1780
+
"jacquard-derive",
1781
+
"jacquard-lexicon",
1782
+
"miette",
1783
+
"rustversion",
1784
+
"serde",
1785
+
"serde_ipld_dagcbor",
1786
+
"thiserror 2.0.14",
1787
+
"unicode-segmentation",
1788
+
]
1789
+
1790
+
[[package]]
1791
+
name = "jacquard-common"
1792
+
version = "0.9.2"
1793
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1794
+
checksum = "df86cb117d9f1c2b0251ba67c3f0e3f963fd22abc6cf8de0e02a7fc846c288ca"
1795
+
dependencies = [
1796
+
"base64",
1797
+
"bon",
1798
+
"bytes",
1799
+
"chrono",
1800
+
"cid",
1801
+
"getrandom 0.2.16",
1802
+
"getrandom 0.3.4",
1803
+
"http",
1804
+
"ipld-core",
1805
+
"k256",
1806
+
"langtag",
1807
+
"miette",
1808
+
"multibase",
1809
+
"multihash",
1810
+
"ouroboros",
1811
+
"p256",
1812
+
"rand 0.9.2",
1813
+
"regex",
1814
+
"regex-lite",
1815
+
"reqwest",
1816
+
"serde",
1817
+
"serde_html_form",
1818
+
"serde_ipld_dagcbor",
1819
+
"serde_json",
1820
+
"signature",
1821
+
"smol_str",
1822
+
"thiserror 2.0.14",
1823
+
"tokio",
1824
+
"tokio-util",
1825
+
"trait-variant",
1826
+
"url",
1827
+
]
1828
+
1829
+
[[package]]
1830
+
name = "jacquard-derive"
1831
+
version = "0.9.2"
1832
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1833
+
checksum = "42ca61a69dc7aa8fb2d7163416514ff7df5d79f2e8b22e269f4610afa85572fe"
1834
+
dependencies = [
1835
+
"heck 0.5.0",
1836
+
"jacquard-lexicon",
1837
+
"proc-macro2",
1838
+
"quote",
1839
+
"syn 2.0.105",
1840
+
]
1841
+
1842
+
[[package]]
1843
+
name = "jacquard-identity"
1844
+
version = "0.9.2"
1845
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1846
+
checksum = "1ef714cacebfca486558a9f8e205daf466bfba0466c4d0c450fd6d0252400a53"
1847
+
dependencies = [
1848
+
"bon",
1849
+
"bytes",
1850
+
"http",
1851
+
"jacquard-api",
1852
+
"jacquard-common",
1853
+
"jacquard-lexicon",
1854
+
"miette",
1855
+
"percent-encoding",
1856
+
"reqwest",
1857
+
"serde",
1858
+
"serde_html_form",
1859
+
"serde_json",
1860
+
"thiserror 2.0.14",
1861
+
"tokio",
1862
+
"trait-variant",
1863
+
"url",
1864
+
"urlencoding",
1865
+
]
1866
+
1867
+
[[package]]
1868
+
name = "jacquard-lexicon"
1869
+
version = "0.9.2"
1870
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1871
+
checksum = "de87f2c938faea1b1f1b32d5b9e0c870e7b5bb5efbf96e3692ae2d8f6b2beb7a"
1872
+
dependencies = [
1873
+
"cid",
1874
+
"dashmap",
1875
+
"heck 0.5.0",
1876
+
"inventory",
1877
+
"jacquard-common",
1878
+
"miette",
1879
+
"multihash",
1880
+
"prettyplease",
1881
+
"proc-macro2",
1882
+
"quote",
1883
+
"serde",
1884
+
"serde_ipld_dagcbor",
1885
+
"serde_json",
1886
+
"serde_repr",
1887
+
"serde_with",
1888
+
"sha2",
1889
+
"syn 2.0.105",
1890
+
"thiserror 2.0.14",
1891
+
"unicode-segmentation",
1892
+
]
1893
+
1894
+
[[package]]
1298
1895
name = "jobserver"
1299
1896
version = "0.1.33"
1300
1897
source = "registry+https://github.com/rust-lang/crates.io-index"
1301
1898
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
1302
1899
dependencies = [
1303
-
"getrandom 0.3.3",
1900
+
"getrandom 0.3.4",
1304
1901
"libc",
1305
1902
]
1306
1903
1307
1904
[[package]]
1905
+
name = "josekit"
1906
+
version = "0.10.3"
1907
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1908
+
checksum = "a808e078330e6af222eb0044b71d4b1ff981bfef43e7bc8133a88234e0c86a0c"
1909
+
dependencies = [
1910
+
"anyhow",
1911
+
"base64",
1912
+
"flate2",
1913
+
"openssl",
1914
+
"regex",
1915
+
"serde",
1916
+
"serde_json",
1917
+
"thiserror 2.0.14",
1918
+
"time",
1919
+
]
1920
+
1921
+
[[package]]
1308
1922
name = "js-sys"
1309
1923
version = "0.3.77"
1310
1924
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1337
1951
]
1338
1952
1339
1953
[[package]]
1954
+
name = "k256"
1955
+
version = "0.13.4"
1956
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1957
+
checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
1958
+
dependencies = [
1959
+
"cfg-if",
1960
+
"ecdsa",
1961
+
"elliptic-curve",
1962
+
"sha2",
1963
+
]
1964
+
1965
+
[[package]]
1966
+
name = "langtag"
1967
+
version = "0.4.0"
1968
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1969
+
checksum = "9ecb4c689a30e48ebeaa14237f34037e300dd072e6ad21a9ec72e810ff3c6600"
1970
+
dependencies = [
1971
+
"serde",
1972
+
"static-regular-grammar",
1973
+
"thiserror 1.0.69",
1974
+
]
1975
+
1976
+
[[package]]
1340
1977
name = "lazy_static"
1341
1978
version = "1.5.0"
1342
1979
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1392
2029
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
1393
2030
dependencies = [
1394
2031
"cfg-if",
1395
-
"windows-targets 0.48.5",
2032
+
"windows-targets 0.52.6",
1396
2033
]
1397
2034
1398
2035
[[package]]
···
1452
2089
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
1453
2090
1454
2091
[[package]]
2092
+
name = "lru-slab"
2093
+
version = "0.1.2"
2094
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2095
+
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
2096
+
2097
+
[[package]]
2098
+
name = "match-lookup"
2099
+
version = "0.1.1"
2100
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2101
+
checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e"
2102
+
dependencies = [
2103
+
"proc-macro2",
2104
+
"quote",
2105
+
"syn 1.0.109",
2106
+
]
2107
+
2108
+
[[package]]
1455
2109
name = "matchers"
1456
2110
version = "0.1.0"
1457
2111
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1483
2137
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
1484
2138
1485
2139
[[package]]
2140
+
name = "miette"
2141
+
version = "7.6.0"
2142
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2143
+
checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7"
2144
+
dependencies = [
2145
+
"cfg-if",
2146
+
"miette-derive",
2147
+
"unicode-width",
2148
+
]
2149
+
2150
+
[[package]]
2151
+
name = "miette-derive"
2152
+
version = "7.6.0"
2153
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2154
+
checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
2155
+
dependencies = [
2156
+
"proc-macro2",
2157
+
"quote",
2158
+
"syn 2.0.105",
2159
+
]
2160
+
2161
+
[[package]]
1486
2162
name = "mime"
1487
2163
version = "0.3.17"
1488
2164
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1501
2177
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
1502
2178
dependencies = [
1503
2179
"adler2",
2180
+
"simd-adler32",
1504
2181
]
1505
2182
1506
2183
[[package]]
···
1510
2187
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
1511
2188
dependencies = [
1512
2189
"libc",
1513
-
"wasi 0.11.1+wasi-snapshot-preview1",
2190
+
"wasi",
1514
2191
"windows-sys 0.59.0",
1515
2192
]
1516
2193
1517
2194
[[package]]
2195
+
name = "multibase"
2196
+
version = "0.9.2"
2197
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2198
+
checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77"
2199
+
dependencies = [
2200
+
"base-x",
2201
+
"base256emoji",
2202
+
"data-encoding",
2203
+
"data-encoding-macro",
2204
+
]
2205
+
2206
+
[[package]]
2207
+
name = "multihash"
2208
+
version = "0.19.3"
2209
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2210
+
checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d"
2211
+
dependencies = [
2212
+
"core2",
2213
+
"serde",
2214
+
"unsigned-varint",
2215
+
]
2216
+
2217
+
[[package]]
1518
2218
name = "nom"
1519
2219
version = "7.1.3"
1520
2220
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1573
2273
]
1574
2274
1575
2275
[[package]]
2276
+
name = "num-conv"
2277
+
version = "0.1.0"
2278
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2279
+
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
2280
+
2281
+
[[package]]
1576
2282
name = "num-integer"
1577
2283
version = "0.1.46"
1578
2284
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1633
2339
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
1634
2340
1635
2341
[[package]]
2342
+
name = "openssl"
2343
+
version = "0.10.75"
2344
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2345
+
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
2346
+
dependencies = [
2347
+
"bitflags",
2348
+
"cfg-if",
2349
+
"foreign-types",
2350
+
"libc",
2351
+
"once_cell",
2352
+
"openssl-macros",
2353
+
"openssl-sys",
2354
+
]
2355
+
2356
+
[[package]]
2357
+
name = "openssl-macros"
2358
+
version = "0.1.1"
2359
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2360
+
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
2361
+
dependencies = [
2362
+
"proc-macro2",
2363
+
"quote",
2364
+
"syn 2.0.105",
2365
+
]
2366
+
2367
+
[[package]]
2368
+
name = "openssl-sys"
2369
+
version = "0.9.111"
2370
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2371
+
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
2372
+
dependencies = [
2373
+
"cc",
2374
+
"libc",
2375
+
"pkg-config",
2376
+
"vcpkg",
2377
+
]
2378
+
2379
+
[[package]]
2380
+
name = "ouroboros"
2381
+
version = "0.18.5"
2382
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2383
+
checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
2384
+
dependencies = [
2385
+
"aliasable",
2386
+
"ouroboros_macro",
2387
+
"static_assertions",
2388
+
]
2389
+
2390
+
[[package]]
2391
+
name = "ouroboros_macro"
2392
+
version = "0.18.5"
2393
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2394
+
checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
2395
+
dependencies = [
2396
+
"heck 0.4.1",
2397
+
"proc-macro2",
2398
+
"proc-macro2-diagnostics",
2399
+
"quote",
2400
+
"syn 2.0.105",
2401
+
]
2402
+
2403
+
[[package]]
1636
2404
name = "overload"
1637
2405
version = "0.1.1"
1638
2406
source = "registry+https://github.com/rust-lang/crates.io-index"
1639
2407
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
1640
2408
1641
2409
[[package]]
2410
+
name = "p256"
2411
+
version = "0.13.2"
2412
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2413
+
checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b"
2414
+
dependencies = [
2415
+
"ecdsa",
2416
+
"elliptic-curve",
2417
+
"primeorder",
2418
+
"sha2",
2419
+
]
2420
+
2421
+
[[package]]
1642
2422
name = "parking"
1643
2423
version = "2.2.1"
1644
2424
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1690
2470
1691
2471
[[package]]
1692
2472
name = "pds_gatekeeper"
1693
-
version = "0.1.0"
2473
+
version = "0.1.2"
1694
2474
dependencies = [
1695
2475
"anyhow",
1696
2476
"aws-lc-rs",
···
1700
2480
"dotenvy",
1701
2481
"handlebars",
1702
2482
"hex",
2483
+
"html-escape",
1703
2484
"hyper-util",
2485
+
"jacquard-common",
2486
+
"jacquard-identity",
2487
+
"josekit",
1704
2488
"jwt-compact",
1705
2489
"lettre",
2490
+
"multibase",
1706
2491
"rand 0.9.2",
2492
+
"reqwest",
1707
2493
"rust-embed",
1708
2494
"rustls",
1709
2495
"scrypt",
···
1716
2502
"tower_governor",
1717
2503
"tracing",
1718
2504
"tracing-subscriber",
2505
+
"urlencoding",
1719
2506
]
1720
2507
1721
2508
[[package]]
···
1764
2551
"pest_meta",
1765
2552
"proc-macro2",
1766
2553
"quote",
1767
-
"syn",
2554
+
"syn 2.0.105",
1768
2555
]
1769
2556
1770
2557
[[package]]
···
1794
2581
dependencies = [
1795
2582
"proc-macro2",
1796
2583
"quote",
1797
-
"syn",
2584
+
"syn 2.0.105",
1798
2585
]
1799
2586
1800
2587
[[package]]
···
1852
2639
]
1853
2640
1854
2641
[[package]]
2642
+
name = "powerfmt"
2643
+
version = "0.2.0"
2644
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2645
+
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
2646
+
2647
+
[[package]]
1855
2648
name = "ppv-lite86"
1856
2649
version = "0.2.21"
1857
2650
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1867
2660
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
1868
2661
dependencies = [
1869
2662
"proc-macro2",
1870
-
"syn",
2663
+
"syn 2.0.105",
2664
+
]
2665
+
2666
+
[[package]]
2667
+
name = "primeorder"
2668
+
version = "0.13.6"
2669
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2670
+
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
2671
+
dependencies = [
2672
+
"elliptic-curve",
2673
+
]
2674
+
2675
+
[[package]]
2676
+
name = "proc-macro-error"
2677
+
version = "1.0.4"
2678
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2679
+
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2680
+
dependencies = [
2681
+
"proc-macro-error-attr",
2682
+
"proc-macro2",
2683
+
"quote",
2684
+
"syn 1.0.109",
2685
+
"version_check",
2686
+
]
2687
+
2688
+
[[package]]
2689
+
name = "proc-macro-error-attr"
2690
+
version = "1.0.4"
2691
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2692
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2693
+
dependencies = [
2694
+
"proc-macro2",
2695
+
"quote",
2696
+
"version_check",
1871
2697
]
1872
2698
1873
2699
[[package]]
···
1880
2706
]
1881
2707
1882
2708
[[package]]
2709
+
name = "proc-macro2-diagnostics"
2710
+
version = "0.10.1"
2711
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2712
+
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
2713
+
dependencies = [
2714
+
"proc-macro2",
2715
+
"quote",
2716
+
"syn 2.0.105",
2717
+
"version_check",
2718
+
"yansi",
2719
+
]
2720
+
2721
+
[[package]]
1883
2722
name = "psm"
1884
2723
version = "0.1.26"
1885
2724
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1898
2737
"libc",
1899
2738
"once_cell",
1900
2739
"raw-cpuid",
1901
-
"wasi 0.11.1+wasi-snapshot-preview1",
2740
+
"wasi",
1902
2741
"web-sys",
1903
2742
"winapi",
1904
2743
]
1905
2744
1906
2745
[[package]]
2746
+
name = "quinn"
2747
+
version = "0.11.9"
2748
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2749
+
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
2750
+
dependencies = [
2751
+
"bytes",
2752
+
"cfg_aliases",
2753
+
"pin-project-lite",
2754
+
"quinn-proto",
2755
+
"quinn-udp",
2756
+
"rustc-hash 2.1.1",
2757
+
"rustls",
2758
+
"socket2",
2759
+
"thiserror 2.0.14",
2760
+
"tokio",
2761
+
"tracing",
2762
+
"web-time",
2763
+
]
2764
+
2765
+
[[package]]
2766
+
name = "quinn-proto"
2767
+
version = "0.11.13"
2768
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2769
+
checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
2770
+
dependencies = [
2771
+
"bytes",
2772
+
"getrandom 0.3.4",
2773
+
"lru-slab",
2774
+
"rand 0.9.2",
2775
+
"ring",
2776
+
"rustc-hash 2.1.1",
2777
+
"rustls",
2778
+
"rustls-pki-types",
2779
+
"slab",
2780
+
"thiserror 2.0.14",
2781
+
"tinyvec",
2782
+
"tracing",
2783
+
"web-time",
2784
+
]
2785
+
2786
+
[[package]]
2787
+
name = "quinn-udp"
2788
+
version = "0.5.14"
2789
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2790
+
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
2791
+
dependencies = [
2792
+
"cfg_aliases",
2793
+
"libc",
2794
+
"once_cell",
2795
+
"socket2",
2796
+
"tracing",
2797
+
"windows-sys 0.59.0",
2798
+
]
2799
+
2800
+
[[package]]
1907
2801
name = "quote"
1908
2802
version = "1.0.40"
1909
2803
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1980
2874
source = "registry+https://github.com/rust-lang/crates.io-index"
1981
2875
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
1982
2876
dependencies = [
1983
-
"getrandom 0.3.3",
2877
+
"getrandom 0.3.4",
1984
2878
]
1985
2879
1986
2880
[[package]]
2881
+
name = "range-traits"
2882
+
version = "0.3.2"
2883
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2884
+
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
2885
+
2886
+
[[package]]
1987
2887
name = "raw-cpuid"
1988
2888
version = "11.5.0"
1989
2889
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2002
2902
]
2003
2903
2004
2904
[[package]]
2905
+
name = "ref-cast"
2906
+
version = "1.0.25"
2907
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2908
+
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
2909
+
dependencies = [
2910
+
"ref-cast-impl",
2911
+
]
2912
+
2913
+
[[package]]
2914
+
name = "ref-cast-impl"
2915
+
version = "1.0.25"
2916
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2917
+
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
2918
+
dependencies = [
2919
+
"proc-macro2",
2920
+
"quote",
2921
+
"syn 2.0.105",
2922
+
]
2923
+
2924
+
[[package]]
2005
2925
name = "regex"
2006
-
version = "1.11.1"
2926
+
version = "1.12.2"
2007
2927
source = "registry+https://github.com/rust-lang/crates.io-index"
2008
-
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
2928
+
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
2009
2929
dependencies = [
2010
2930
"aho-corasick",
2011
2931
"memchr",
2012
-
"regex-automata 0.4.9",
2932
+
"regex-automata 0.4.13",
2013
2933
"regex-syntax 0.8.5",
2014
2934
]
2015
2935
···
2024
2944
2025
2945
[[package]]
2026
2946
name = "regex-automata"
2027
-
version = "0.4.9"
2947
+
version = "0.4.13"
2028
2948
source = "registry+https://github.com/rust-lang/crates.io-index"
2029
-
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
2949
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
2030
2950
dependencies = [
2031
2951
"aho-corasick",
2032
2952
"memchr",
···
2034
2954
]
2035
2955
2036
2956
[[package]]
2957
+
name = "regex-lite"
2958
+
version = "0.1.8"
2959
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2960
+
checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da"
2961
+
2962
+
[[package]]
2037
2963
name = "regex-syntax"
2038
2964
version = "0.6.29"
2039
2965
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2046
2972
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
2047
2973
2048
2974
[[package]]
2975
+
name = "reqwest"
2976
+
version = "0.12.24"
2977
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2978
+
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
2979
+
dependencies = [
2980
+
"async-compression",
2981
+
"base64",
2982
+
"bytes",
2983
+
"encoding_rs",
2984
+
"futures-core",
2985
+
"futures-util",
2986
+
"h2",
2987
+
"http",
2988
+
"http-body",
2989
+
"http-body-util",
2990
+
"hyper",
2991
+
"hyper-rustls",
2992
+
"hyper-util",
2993
+
"js-sys",
2994
+
"log",
2995
+
"mime",
2996
+
"percent-encoding",
2997
+
"pin-project-lite",
2998
+
"quinn",
2999
+
"rustls",
3000
+
"rustls-pki-types",
3001
+
"serde",
3002
+
"serde_json",
3003
+
"serde_urlencoded",
3004
+
"sync_wrapper",
3005
+
"tokio",
3006
+
"tokio-rustls",
3007
+
"tokio-util",
3008
+
"tower",
3009
+
"tower-http",
3010
+
"tower-service",
3011
+
"url",
3012
+
"wasm-bindgen",
3013
+
"wasm-bindgen-futures",
3014
+
"wasm-streams",
3015
+
"web-sys",
3016
+
"webpki-roots 1.0.2",
3017
+
]
3018
+
3019
+
[[package]]
3020
+
name = "rfc6979"
3021
+
version = "0.4.0"
3022
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3023
+
checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
3024
+
dependencies = [
3025
+
"hmac",
3026
+
"subtle",
3027
+
]
3028
+
3029
+
[[package]]
2049
3030
name = "ring"
2050
3031
version = "0.17.14"
2051
3032
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2099
3080
"proc-macro2",
2100
3081
"quote",
2101
3082
"rust-embed-utils",
2102
-
"syn",
3083
+
"syn 2.0.105",
2103
3084
"walkdir",
2104
3085
]
2105
3086
···
2127
3108
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
2128
3109
2129
3110
[[package]]
3111
+
name = "rustc-hash"
3112
+
version = "2.1.1"
3113
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3114
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3115
+
3116
+
[[package]]
2130
3117
name = "rustix"
2131
3118
version = "0.38.44"
2132
3119
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2136
3123
"errno",
2137
3124
"libc",
2138
3125
"linux-raw-sys",
2139
-
"windows-sys 0.52.0",
3126
+
"windows-sys 0.59.0",
2140
3127
]
2141
3128
2142
3129
[[package]]
···
2161
3148
source = "registry+https://github.com/rust-lang/crates.io-index"
2162
3149
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
2163
3150
dependencies = [
3151
+
"web-time",
2164
3152
"zeroize",
2165
3153
]
2166
3154
···
2207
3195
]
2208
3196
2209
3197
[[package]]
3198
+
name = "schemars"
3199
+
version = "0.9.0"
3200
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3201
+
checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
3202
+
dependencies = [
3203
+
"dyn-clone",
3204
+
"ref-cast",
3205
+
"serde",
3206
+
"serde_json",
3207
+
]
3208
+
3209
+
[[package]]
3210
+
name = "schemars"
3211
+
version = "1.1.0"
3212
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3213
+
checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289"
3214
+
dependencies = [
3215
+
"dyn-clone",
3216
+
"ref-cast",
3217
+
"serde",
3218
+
"serde_json",
3219
+
]
3220
+
3221
+
[[package]]
2210
3222
name = "scopeguard"
2211
3223
version = "1.2.0"
2212
3224
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2225
3237
]
2226
3238
2227
3239
[[package]]
3240
+
name = "sec1"
3241
+
version = "0.7.3"
3242
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3243
+
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
3244
+
dependencies = [
3245
+
"base16ct",
3246
+
"der",
3247
+
"generic-array",
3248
+
"pkcs8",
3249
+
"subtle",
3250
+
"zeroize",
3251
+
]
3252
+
3253
+
[[package]]
2228
3254
name = "secp256k1"
2229
3255
version = "0.28.2"
2230
3256
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2244
3270
2245
3271
[[package]]
2246
3272
name = "serde"
2247
-
version = "1.0.219"
3273
+
version = "1.0.228"
3274
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3275
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
3276
+
dependencies = [
3277
+
"serde_core",
3278
+
"serde_derive",
3279
+
]
3280
+
3281
+
[[package]]
3282
+
name = "serde_bytes"
3283
+
version = "0.11.19"
3284
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3285
+
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
3286
+
dependencies = [
3287
+
"serde",
3288
+
"serde_core",
3289
+
]
3290
+
3291
+
[[package]]
3292
+
name = "serde_core"
3293
+
version = "1.0.228"
2248
3294
source = "registry+https://github.com/rust-lang/crates.io-index"
2249
-
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
3295
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
2250
3296
dependencies = [
2251
3297
"serde_derive",
2252
3298
]
2253
3299
2254
3300
[[package]]
2255
3301
name = "serde_derive"
2256
-
version = "1.0.219"
3302
+
version = "1.0.228"
2257
3303
source = "registry+https://github.com/rust-lang/crates.io-index"
2258
-
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
3304
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
2259
3305
dependencies = [
2260
3306
"proc-macro2",
2261
3307
"quote",
2262
-
"syn",
3308
+
"syn 2.0.105",
3309
+
]
3310
+
3311
+
[[package]]
3312
+
name = "serde_html_form"
3313
+
version = "0.2.8"
3314
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3315
+
checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f"
3316
+
dependencies = [
3317
+
"form_urlencoded",
3318
+
"indexmap 2.10.0",
3319
+
"itoa",
3320
+
"ryu",
3321
+
"serde_core",
3322
+
]
3323
+
3324
+
[[package]]
3325
+
name = "serde_ipld_dagcbor"
3326
+
version = "0.6.4"
3327
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3328
+
checksum = "46182f4f08349a02b45c998ba3215d3f9de826246ba02bb9dddfe9a2a2100778"
3329
+
dependencies = [
3330
+
"cbor4ii",
3331
+
"ipld-core",
3332
+
"scopeguard",
3333
+
"serde",
2263
3334
]
2264
3335
2265
3336
[[package]]
2266
3337
name = "serde_json"
2267
-
version = "1.0.142"
3338
+
version = "1.0.145"
2268
3339
source = "registry+https://github.com/rust-lang/crates.io-index"
2269
-
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
3340
+
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
2270
3341
dependencies = [
3342
+
"indexmap 2.10.0",
2271
3343
"itoa",
2272
3344
"memchr",
2273
3345
"ryu",
2274
3346
"serde",
3347
+
"serde_core",
2275
3348
]
2276
3349
2277
3350
[[package]]
···
2285
3358
]
2286
3359
2287
3360
[[package]]
3361
+
name = "serde_repr"
3362
+
version = "0.1.20"
3363
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3364
+
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
3365
+
dependencies = [
3366
+
"proc-macro2",
3367
+
"quote",
3368
+
"syn 2.0.105",
3369
+
]
3370
+
3371
+
[[package]]
2288
3372
name = "serde_urlencoded"
2289
3373
version = "0.7.1"
2290
3374
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2297
3381
]
2298
3382
2299
3383
[[package]]
3384
+
name = "serde_with"
3385
+
version = "3.16.0"
3386
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3387
+
checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1"
3388
+
dependencies = [
3389
+
"base64",
3390
+
"chrono",
3391
+
"hex",
3392
+
"indexmap 1.9.3",
3393
+
"indexmap 2.10.0",
3394
+
"schemars 0.9.0",
3395
+
"schemars 1.1.0",
3396
+
"serde_core",
3397
+
"serde_json",
3398
+
"serde_with_macros",
3399
+
"time",
3400
+
]
3401
+
3402
+
[[package]]
3403
+
name = "serde_with_macros"
3404
+
version = "3.16.0"
3405
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3406
+
checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b"
3407
+
dependencies = [
3408
+
"darling 0.21.3",
3409
+
"proc-macro2",
3410
+
"quote",
3411
+
"syn 2.0.105",
3412
+
]
3413
+
3414
+
[[package]]
2300
3415
name = "sha1"
2301
3416
version = "0.10.6"
2302
3417
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2353
3468
]
2354
3469
2355
3470
[[package]]
3471
+
name = "simd-adler32"
3472
+
version = "0.3.7"
3473
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3474
+
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
3475
+
3476
+
[[package]]
2356
3477
name = "slab"
2357
3478
version = "0.4.11"
2358
3479
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2368
3489
]
2369
3490
2370
3491
[[package]]
3492
+
name = "smol_str"
3493
+
version = "0.3.4"
3494
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3495
+
checksum = "3498b0a27f93ef1402f20eefacfaa1691272ac4eca1cdc8c596cb0a245d6cbf5"
3496
+
dependencies = [
3497
+
"borsh",
3498
+
"serde_core",
3499
+
]
3500
+
3501
+
[[package]]
2371
3502
name = "socket2"
2372
3503
version = "0.6.0"
2373
3504
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2437
3568
"futures-util",
2438
3569
"hashbrown 0.15.5",
2439
3570
"hashlink",
2440
-
"indexmap",
3571
+
"indexmap 2.10.0",
2441
3572
"log",
2442
3573
"memchr",
2443
3574
"once_cell",
···
2465
3596
"quote",
2466
3597
"sqlx-core",
2467
3598
"sqlx-macros-core",
2468
-
"syn",
3599
+
"syn 2.0.105",
2469
3600
]
2470
3601
2471
3602
[[package]]
···
2476
3607
dependencies = [
2477
3608
"dotenvy",
2478
3609
"either",
2479
-
"heck",
3610
+
"heck 0.5.0",
2480
3611
"hex",
2481
3612
"once_cell",
2482
3613
"proc-macro2",
···
2488
3619
"sqlx-mysql",
2489
3620
"sqlx-postgres",
2490
3621
"sqlx-sqlite",
2491
-
"syn",
3622
+
"syn 2.0.105",
2492
3623
"tokio",
2493
3624
"url",
2494
3625
]
···
2619
3750
]
2620
3751
2621
3752
[[package]]
3753
+
name = "static-regular-grammar"
3754
+
version = "2.0.2"
3755
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3756
+
checksum = "4f4a6c40247579acfbb138c3cd7de3dab113ab4ac6227f1b7de7d626ee667957"
3757
+
dependencies = [
3758
+
"abnf",
3759
+
"btree-range-map",
3760
+
"ciborium",
3761
+
"hex_fmt",
3762
+
"indoc",
3763
+
"proc-macro-error",
3764
+
"proc-macro2",
3765
+
"quote",
3766
+
"serde",
3767
+
"sha2",
3768
+
"syn 2.0.105",
3769
+
"thiserror 1.0.69",
3770
+
]
3771
+
3772
+
[[package]]
3773
+
name = "static_assertions"
3774
+
version = "1.1.0"
3775
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3776
+
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
3777
+
3778
+
[[package]]
2622
3779
name = "stringprep"
2623
3780
version = "0.1.5"
2624
3781
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2643
3800
2644
3801
[[package]]
2645
3802
name = "syn"
3803
+
version = "1.0.109"
3804
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3805
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
3806
+
dependencies = [
3807
+
"proc-macro2",
3808
+
"quote",
3809
+
"unicode-ident",
3810
+
]
3811
+
3812
+
[[package]]
3813
+
name = "syn"
2646
3814
version = "2.0.105"
2647
3815
source = "registry+https://github.com/rust-lang/crates.io-index"
2648
3816
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
···
2657
3825
version = "1.0.2"
2658
3826
source = "registry+https://github.com/rust-lang/crates.io-index"
2659
3827
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
3828
+
dependencies = [
3829
+
"futures-core",
3830
+
]
2660
3831
2661
3832
[[package]]
2662
3833
name = "synstructure"
···
2666
3837
dependencies = [
2667
3838
"proc-macro2",
2668
3839
"quote",
2669
-
"syn",
3840
+
"syn 2.0.105",
3841
+
]
3842
+
3843
+
[[package]]
3844
+
name = "system-configuration"
3845
+
version = "0.6.1"
3846
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3847
+
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
3848
+
dependencies = [
3849
+
"bitflags",
3850
+
"core-foundation",
3851
+
"system-configuration-sys",
3852
+
]
3853
+
3854
+
[[package]]
3855
+
name = "system-configuration-sys"
3856
+
version = "0.6.0"
3857
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3858
+
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
3859
+
dependencies = [
3860
+
"core-foundation-sys",
3861
+
"libc",
2670
3862
]
2671
3863
2672
3864
[[package]]
···
2695
3887
dependencies = [
2696
3888
"proc-macro2",
2697
3889
"quote",
2698
-
"syn",
3890
+
"syn 2.0.105",
2699
3891
]
2700
3892
2701
3893
[[package]]
···
2706
3898
dependencies = [
2707
3899
"proc-macro2",
2708
3900
"quote",
2709
-
"syn",
3901
+
"syn 2.0.105",
2710
3902
]
2711
3903
2712
3904
[[package]]
···
2719
3911
]
2720
3912
2721
3913
[[package]]
3914
+
name = "time"
3915
+
version = "0.3.44"
3916
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3917
+
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
3918
+
dependencies = [
3919
+
"deranged",
3920
+
"itoa",
3921
+
"num-conv",
3922
+
"powerfmt",
3923
+
"serde",
3924
+
"time-core",
3925
+
"time-macros",
3926
+
]
3927
+
3928
+
[[package]]
3929
+
name = "time-core"
3930
+
version = "0.1.6"
3931
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3932
+
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
3933
+
3934
+
[[package]]
3935
+
name = "time-macros"
3936
+
version = "0.2.24"
3937
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3938
+
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
3939
+
dependencies = [
3940
+
"num-conv",
3941
+
"time-core",
3942
+
]
3943
+
3944
+
[[package]]
2722
3945
name = "tinystr"
2723
3946
version = "0.8.1"
2724
3947
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2770
3993
dependencies = [
2771
3994
"proc-macro2",
2772
3995
"quote",
2773
-
"syn",
3996
+
"syn 2.0.105",
2774
3997
]
2775
3998
2776
3999
[[package]]
···
2796
4019
2797
4020
[[package]]
2798
4021
name = "tokio-util"
2799
-
version = "0.7.15"
4022
+
version = "0.7.17"
2800
4023
source = "registry+https://github.com/rust-lang/crates.io-index"
2801
-
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
4024
+
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
2802
4025
dependencies = [
2803
4026
"bytes",
2804
4027
"futures-core",
···
2844
4067
dependencies = [
2845
4068
"futures-core",
2846
4069
"futures-util",
2847
-
"indexmap",
4070
+
"indexmap 2.10.0",
2848
4071
"pin-project-lite",
2849
4072
"slab",
2850
4073
"sync_wrapper",
···
2865
4088
"bitflags",
2866
4089
"bytes",
2867
4090
"futures-core",
4091
+
"futures-util",
2868
4092
"http",
2869
4093
"http-body",
4094
+
"iri-string",
2870
4095
"pin-project-lite",
2871
4096
"tokio",
2872
4097
"tokio-util",
4098
+
"tower",
2873
4099
"tower-layer",
2874
4100
"tower-service",
2875
4101
]
···
2923
4149
dependencies = [
2924
4150
"proc-macro2",
2925
4151
"quote",
2926
-
"syn",
4152
+
"syn 2.0.105",
2927
4153
]
2928
4154
2929
4155
[[package]]
···
2966
4192
]
2967
4193
2968
4194
[[package]]
4195
+
name = "trait-variant"
4196
+
version = "0.1.2"
4197
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4198
+
checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7"
4199
+
dependencies = [
4200
+
"proc-macro2",
4201
+
"quote",
4202
+
"syn 2.0.105",
4203
+
]
4204
+
4205
+
[[package]]
2969
4206
name = "try-lock"
2970
4207
version = "0.2.5"
2971
4208
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3011
4248
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
3012
4249
3013
4250
[[package]]
4251
+
name = "unicode-segmentation"
4252
+
version = "1.12.0"
4253
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4254
+
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
4255
+
4256
+
[[package]]
4257
+
name = "unicode-width"
4258
+
version = "0.1.14"
4259
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4260
+
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
4261
+
4262
+
[[package]]
4263
+
name = "unsigned-varint"
4264
+
version = "0.8.0"
4265
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4266
+
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
4267
+
4268
+
[[package]]
3014
4269
name = "untrusted"
3015
4270
version = "0.7.1"
3016
4271
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3031
4286
"form_urlencoded",
3032
4287
"idna",
3033
4288
"percent-encoding",
4289
+
"serde",
3034
4290
]
3035
4291
3036
4292
[[package]]
4293
+
name = "urlencoding"
4294
+
version = "2.1.3"
4295
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4296
+
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
4297
+
4298
+
[[package]]
4299
+
name = "utf8-width"
4300
+
version = "0.1.8"
4301
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4302
+
checksum = "1292c0d970b54115d14f2492fe0170adf21d68a1de108eebc51c1df4f346a091"
4303
+
4304
+
[[package]]
3037
4305
name = "utf8_iter"
3038
4306
version = "1.0.4"
3039
4307
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3083
4351
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
3084
4352
3085
4353
[[package]]
3086
-
name = "wasi"
3087
-
version = "0.14.2+wasi-0.2.4"
4354
+
name = "wasip2"
4355
+
version = "1.0.1+wasi-0.2.4"
3088
4356
source = "registry+https://github.com/rust-lang/crates.io-index"
3089
-
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
4357
+
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
3090
4358
dependencies = [
3091
-
"wit-bindgen-rt",
4359
+
"wit-bindgen",
3092
4360
]
3093
4361
3094
4362
[[package]]
···
3119
4387
"log",
3120
4388
"proc-macro2",
3121
4389
"quote",
3122
-
"syn",
4390
+
"syn 2.0.105",
3123
4391
"wasm-bindgen-shared",
3124
4392
]
3125
4393
3126
4394
[[package]]
4395
+
name = "wasm-bindgen-futures"
4396
+
version = "0.4.50"
4397
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4398
+
checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
4399
+
dependencies = [
4400
+
"cfg-if",
4401
+
"js-sys",
4402
+
"once_cell",
4403
+
"wasm-bindgen",
4404
+
"web-sys",
4405
+
]
4406
+
4407
+
[[package]]
3127
4408
name = "wasm-bindgen-macro"
3128
4409
version = "0.2.100"
3129
4410
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3141
4422
dependencies = [
3142
4423
"proc-macro2",
3143
4424
"quote",
3144
-
"syn",
4425
+
"syn 2.0.105",
3145
4426
"wasm-bindgen-backend",
3146
4427
"wasm-bindgen-shared",
3147
4428
]
···
3153
4434
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
3154
4435
dependencies = [
3155
4436
"unicode-ident",
4437
+
]
4438
+
4439
+
[[package]]
4440
+
name = "wasm-streams"
4441
+
version = "0.4.2"
4442
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4443
+
checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
4444
+
dependencies = [
4445
+
"futures-util",
4446
+
"js-sys",
4447
+
"wasm-bindgen",
4448
+
"wasm-bindgen-futures",
4449
+
"web-sys",
3156
4450
]
3157
4451
3158
4452
[[package]]
···
3254
4548
dependencies = [
3255
4549
"windows-implement",
3256
4550
"windows-interface",
3257
-
"windows-link",
4551
+
"windows-link 0.1.3",
3258
4552
"windows-result",
3259
4553
"windows-strings",
3260
4554
]
···
3267
4561
dependencies = [
3268
4562
"proc-macro2",
3269
4563
"quote",
3270
-
"syn",
4564
+
"syn 2.0.105",
3271
4565
]
3272
4566
3273
4567
[[package]]
···
3278
4572
dependencies = [
3279
4573
"proc-macro2",
3280
4574
"quote",
3281
-
"syn",
4575
+
"syn 2.0.105",
3282
4576
]
3283
4577
3284
4578
[[package]]
···
3288
4582
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
3289
4583
3290
4584
[[package]]
4585
+
name = "windows-link"
4586
+
version = "0.2.1"
4587
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4588
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
4589
+
4590
+
[[package]]
4591
+
name = "windows-registry"
4592
+
version = "0.5.3"
4593
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4594
+
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
4595
+
dependencies = [
4596
+
"windows-link 0.1.3",
4597
+
"windows-result",
4598
+
"windows-strings",
4599
+
]
4600
+
4601
+
[[package]]
3291
4602
name = "windows-result"
3292
4603
version = "0.3.4"
3293
4604
source = "registry+https://github.com/rust-lang/crates.io-index"
3294
4605
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
3295
4606
dependencies = [
3296
-
"windows-link",
4607
+
"windows-link 0.1.3",
3297
4608
]
3298
4609
3299
4610
[[package]]
···
3302
4613
source = "registry+https://github.com/rust-lang/crates.io-index"
3303
4614
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
3304
4615
dependencies = [
3305
-
"windows-link",
4616
+
"windows-link 0.1.3",
3306
4617
]
3307
4618
3308
4619
[[package]]
···
3454
4765
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
3455
4766
3456
4767
[[package]]
3457
-
name = "wit-bindgen-rt"
3458
-
version = "0.39.0"
4768
+
name = "wit-bindgen"
4769
+
version = "0.46.0"
3459
4770
source = "registry+https://github.com/rust-lang/crates.io-index"
3460
-
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
3461
-
dependencies = [
3462
-
"bitflags",
3463
-
]
4771
+
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
3464
4772
3465
4773
[[package]]
3466
4774
name = "writeable"
···
3469
4777
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
3470
4778
3471
4779
[[package]]
4780
+
name = "yansi"
4781
+
version = "1.0.1"
4782
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4783
+
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
4784
+
4785
+
[[package]]
3472
4786
name = "yoke"
3473
4787
version = "0.8.0"
3474
4788
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3488
4802
dependencies = [
3489
4803
"proc-macro2",
3490
4804
"quote",
3491
-
"syn",
4805
+
"syn 2.0.105",
3492
4806
"synstructure",
3493
4807
]
3494
4808
···
3509
4823
dependencies = [
3510
4824
"proc-macro2",
3511
4825
"quote",
3512
-
"syn",
4826
+
"syn 2.0.105",
3513
4827
]
3514
4828
3515
4829
[[package]]
···
3529
4843
dependencies = [
3530
4844
"proc-macro2",
3531
4845
"quote",
3532
-
"syn",
4846
+
"syn 2.0.105",
3533
4847
"synstructure",
3534
4848
]
3535
4849
···
3550
4864
dependencies = [
3551
4865
"proc-macro2",
3552
4866
"quote",
3553
-
"syn",
4867
+
"syn 2.0.105",
3554
4868
]
3555
4869
3556
4870
[[package]]
···
3583
4897
dependencies = [
3584
4898
"proc-macro2",
3585
4899
"quote",
3586
-
"syn",
4900
+
"syn 2.0.105",
3587
4901
]
3588
4902
3589
4903
[[package]]
+13
-6
Cargo.toml
+13
-6
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", default-features = false, features = ["pool", "tokio1-rustls", "smtp-transport", "hostname", "builder"] }
22
-
#lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] }
22
+
#Leaveing these two cause I think it is needed by the email crate for ssl
23
23
aws-lc-rs = "1.13.0"
24
+
rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] }
24
25
lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] }
25
-
rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] }
26
26
handlebars = { version = "6.3.2", features = ["rust-embed"] }
27
27
rust-embed = "8.7.2"
28
28
axum-template = { version = "3.0.0", features = ["handlebars"] }
29
29
rand = "0.9.2"
30
30
anyhow = "1.0.99"
31
-
chrono = "0.4.41"
31
+
chrono = { version = "0.4.42", features = ["default", "serde"] }
32
32
sha2 = "0.10"
33
+
jacquard-common = "0.9.2"
34
+
jacquard-identity = "0.9.2"
35
+
multibase = "0.9.2"
36
+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
37
+
urlencoding = "2.1"
38
+
html-escape = "0.2.13"
39
+
josekit = "0.10.3"
+21
LICENSE.md
+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.
+118
-23
README.md
+118
-23
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
···
37
64
```yml
38
65
gatekeeper:
39
66
container_name: gatekeeper
40
-
image: fatfingers23/pds_gatekeeper:arm-latest
67
+
image: fatfingers23/pds_gatekeeper:latest
41
68
network_mode: host
42
69
restart: unless-stopped
43
70
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
···
49
76
- pds
50
77
```
51
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
+
```
123
+
52
124
## Caddy setup
53
125
54
126
For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add
55
127
in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile).
56
128
This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS.
57
129
58
-
```caddyfile
130
+
```
59
131
@gatekeeper {
60
-
path /xrpc/com.atproto.server.getSession
61
-
path /xrpc/com.atproto.server.updateEmail
62
-
path /xrpc/com.atproto.server.createSession
63
-
path /@atproto/oauth-provider/~api/sign-in
132
+
path /xrpc/com.atproto.server.getSession
133
+
path /xrpc/com.atproto.server.describeServer
134
+
path /xrpc/com.atproto.server.updateEmail
135
+
path /xrpc/com.atproto.server.createSession
136
+
path /xrpc/com.atproto.server.createAccount
137
+
path /@atproto/oauth-provider/~api/sign-in
138
+
path /gate/*
64
139
}
65
140
66
141
handle @gatekeeper {
67
-
reverse_proxy http://localhost:8080
68
-
}
142
+
reverse_proxy http://localhost:8080
143
+
}
69
144
70
-
reverse_proxy http://localhost:3000
145
+
reverse_proxy http://localhost:3000
71
146
```
72
147
73
148
If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
74
149
`localhost:8081` (or w/e port you want).
75
150
76
-
```caddyfile
151
+
```
77
152
http://*.localhost:8082, http://localhost:8082 {
78
-
@gatekeeper {
79
-
path /xrpc/com.atproto.server.getSession
80
-
path /xrpc/com.atproto.server.updateEmail
81
-
path /xrpc/com.atproto.server.createSession
82
-
path /@atproto/oauth-provider/~api/sign-in
83
-
}
84
-
85
-
handle @gatekeeper {
86
-
reverse_proxy http://localhost:8080
87
-
}
153
+
@gatekeeper {
154
+
path /xrpc/com.atproto.server.getSession
155
+
path /xrpc/com.atproto.server.describeServer
156
+
path /xrpc/com.atproto.server.updateEmail
157
+
path /xrpc/com.atproto.server.createSession
158
+
path /xrpc/com.atproto.server.createAccount
159
+
path /@atproto/oauth-provider/~api/sign-in
160
+
path /gate/*
161
+
}
88
162
89
-
reverse_proxy http://localhost:3000
163
+
handle @gatekeeper {
164
+
#This is the address for PDS gatekeeper, default is 8080
165
+
reverse_proxy http://localhost:8080
166
+
#Makes sure the cloudflare ip is proxied and able to be picked up by pds gatekeeper
167
+
header_up X-Forwarded-For {http.request.header.CF-Connecting-IP}
168
+
}
169
+
reverse_proxy http://localhost:3000
90
170
}
91
171
92
172
```
···
105
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
106
186
same.
107
187
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
+
108
191
`PDS_BASE_URL` - Base url of the PDS. You most likely want `https://localhost:3000` which is also the default
109
192
110
193
`GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1`
111
194
112
195
`GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080`
196
+
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
+
+22
-21
examples/Caddyfile
+22
-21
examples/Caddyfile
···
1
1
{
2
-
email youremail@myemail.com
3
-
on_demand_tls {
4
-
ask http://localhost:3000/tls-check
5
-
}
2
+
email youremail@myemail.com
3
+
on_demand_tls {
4
+
ask http://localhost:3000/tls-check
5
+
}
6
6
}
7
7
8
8
*.yourpds.com, yourpds.com {
9
-
tls {
10
-
on_demand
9
+
tls {
10
+
on_demand
11
11
}
12
-
# You'll most likely just want from here to....
13
-
@gatekeeper {
14
-
path /xrpc/com.atproto.server.getSession
15
-
path /xrpc/com.atproto.server.updateEmail
16
-
path /xrpc/com.atproto.server.createSession
17
-
path /@atproto/oauth-provider/~api/sign-in
18
-
}
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
+
}
19
22
20
-
handle @gatekeeper {
21
-
#This is the address for PDS gatekeeper, default is 8080
22
-
reverse_proxy http://localhost:8080
23
-
}
23
+
handle @gatekeeper {
24
+
#This is the address for PDS gatekeeper, default is 8080
25
+
reverse_proxy http://localhost:8080
26
+
}
24
27
25
-
reverse_proxy http://localhost:3000
26
-
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
28
+
reverse_proxy http://localhost:3000
29
+
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
27
30
}
28
-
29
-
+1
-1
examples/compose.yml
+1
-1
examples/compose.yml
···
39
39
WATCHTOWER_SCHEDULE: "@midnight"
40
40
gatekeeper:
41
41
container_name: gatekeeper
42
-
image: fatfingers23/pds_gatekeeper:arm-latest
42
+
image: fatfingers23/pds_gatekeeper:latest
43
43
network_mode: host
44
44
restart: unless-stopped
45
45
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
+73
examples/coolify-compose.yml
+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
+166
html_templates/captcha.hbs
···
1
+
<html lang="en" class=" ">
2
+
<head>
3
+
<meta charset="utf-8"/>
4
+
<meta
5
+
name="viewport"
6
+
content="width=device-width, initial-scale=1, minimum-scale=1, viewport-fit=cover"
7
+
/>
8
+
<meta name="referrer" content="origin-when-cross-origin"/>
9
+
10
+
<title>
11
+
{{pds}} - Captcha
12
+
</title>
13
+
<style>
14
+
:root,
15
+
:root.light-mode {
16
+
--brand-color: rgb(16, 131, 254);
17
+
--primary-color: rgb(7, 10, 13);
18
+
--secondary-color: rgb(66, 86, 108);
19
+
--bg-primary-color: rgb(255, 255, 255);
20
+
--bg-secondary-color: rgb(240, 242, 245);
21
+
}
22
+
23
+
@media (prefers-color-scheme: dark) {
24
+
:root {
25
+
--brand-color: rgb(16, 131, 254);
26
+
--primary-color: rgb(255, 255, 255);
27
+
--secondary-color: rgb(133, 152, 173);
28
+
--bg-primary-color: rgb(7, 10, 13);
29
+
--bg-secondary-color: rgb(13, 18, 23);
30
+
}
31
+
}
32
+
33
+
:root.dark-mode {
34
+
--brand-color: rgb(16, 131, 254);
35
+
--primary-color: rgb(255, 255, 255);
36
+
--secondary-color: rgb(133, 152, 173);
37
+
--bg-primary-color: rgb(7, 10, 13);
38
+
--bg-secondary-color: rgb(13, 18, 23);
39
+
}
40
+
41
+
body {
42
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica,
43
+
Arial, sans-serif;
44
+
background: var(--bg-primary-color);
45
+
color: var(--primary-color);
46
+
text-rendering: optimizeLegibility;
47
+
-webkit-font-smoothing: antialiased;
48
+
}
49
+
50
+
.info {
51
+
border-radius: 8px;
52
+
padding: 12px 14px;
53
+
letter-spacing: 0.25px;
54
+
font-weight: 400;
55
+
background-color: var(--bg-secondary-color);
56
+
color: var(--secondary-color);
57
+
}
58
+
59
+
.gate-page,
60
+
.error-page {
61
+
margin: 0;
62
+
margin-top: 10px;
63
+
display: flex;
64
+
justify-content: center;
65
+
}
66
+
67
+
.gate-page .main,
68
+
.error-page .main {
69
+
max-width: 600px;
70
+
width: 100%;
71
+
display: flex;
72
+
flex-direction: column;
73
+
}
74
+
75
+
.gate-page .main {
76
+
padding: 0;
77
+
}
78
+
79
+
.error-page .main {
80
+
padding: 20px;
81
+
}
82
+
83
+
.gate-page .main > :not(:first-child),
84
+
.error-page .main > :not(:first-child) {
85
+
margin-top: 20px;
86
+
}
87
+
88
+
.gate-page #gate-form {
89
+
margin: 0;
90
+
}
91
+
92
+
.gate-page #hcaptcha {
93
+
display: flex;
94
+
justify-content: center;
95
+
}
96
+
97
+
.pds-title {
98
+
font-size: 2.25rem;
99
+
font-weight: 700;
100
+
line-height: 1.2;
101
+
text-align: center;
102
+
color: var(--primary-color);
103
+
margin: 10px 0 16px;
104
+
}
105
+
106
+
</style>
107
+
108
+
<script type="text/javascript">
109
+
function onCaptchaReady() {
110
+
const theme = document.documentElement.classList.contains('dark-mode') ?
111
+
'dark' :
112
+
document.documentElement.classList.contains('light-mode') ?
113
+
'light' :
114
+
window.matchMedia('(prefers-color-scheme: dark)').matches ?
115
+
'dark' :
116
+
'light'
117
+
hcaptcha.render('hcaptcha', {theme});
118
+
}
119
+
120
+
function onCaptchaComplete() {
121
+
setTimeout(function () {
122
+
document.getElementById('gate-form').submit();
123
+
}, 1000);
124
+
}
125
+
126
+
function onCaptchaError() {
127
+
const url = new URL(location.href);
128
+
url.searchParams.set('error', 'true');
129
+
location.assign(url.search);
130
+
}
131
+
132
+
function onCaptchaExpired() {
133
+
const url = new URL(location.href);
134
+
url.searchParams.set('error', 'expired');
135
+
location.assign(url.search);
136
+
}
137
+
</script>
138
+
<script
139
+
src="https://js.hcaptcha.com/1/api.js?render=explicit&onload=onCaptchaReady"
140
+
async
141
+
defer
142
+
></script>
143
+
<link rel="stylesheet" href="/gate/signup/assets/common.css"/>
144
+
</head>
145
+
146
+
<body class="gate-page">
147
+
<div class="main">
148
+
<div class="pds-title">{{pds}}</div>
149
+
<form id="gate-form" action="" method="POST">
150
+
<div
151
+
id="hcaptcha"
152
+
data-sitekey="{{captcha_site_key}}"
153
+
data-callback="onCaptchaComplete"
154
+
data-error-callback="onCaptchaError"
155
+
data-expired-callback="onCaptchaExpired"
156
+
data-chalexpired-callback="onCaptchaExpired"
157
+
></div>
158
+
<input type="hidden" name="redirect_url" value="{{redirect_url}}"/>
159
+
</form>
160
+
{{#if error_message }}
161
+
<div class="info">{{error_message}}</div>
162
+
{{/if}}
163
+
164
+
</div>
165
+
</body>
166
+
</html>
+1
-1
justfile
+1
-1
justfile
+10
migrations/20251126000000_gate_codes.sql
+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
+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(¶ms.handle),
139
+
url_encode(¶ms.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(¶ms.handle),
154
+
url_encode(¶ms.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(¶ms.handle),
170
+
url_encode(¶ms.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(¶ms.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(¶ms.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(¶ms.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
+177
-14
src/helpers.rs
···
1
1
use crate::AppState;
2
2
use crate::helpers::TokenCheckError::InvalidToken;
3
3
use anyhow::anyhow;
4
-
use axum::body::{Body, to_bytes};
5
-
use axum::extract::Request;
6
-
use axum::http::header::CONTENT_TYPE;
7
-
use axum::http::{HeaderMap, StatusCode, Uri};
8
-
use axum::response::{IntoResponse, Response};
4
+
use axum::{
5
+
body::{Body, to_bytes},
6
+
extract::Request,
7
+
http::header::CONTENT_TYPE,
8
+
http::{HeaderMap, StatusCode, Uri},
9
+
response::{IntoResponse, Response},
10
+
};
9
11
use axum_template::TemplateEngine;
10
12
use chrono::Utc;
11
-
use lettre::message::{MultiPart, SinglePart, header};
12
-
use lettre::{AsyncTransport, Message};
13
+
use jacquard_common::{
14
+
service_auth, service_auth::PublicKey, types::did::Did, types::did_doc::VerificationMethod,
15
+
types::nsid::Nsid,
16
+
};
17
+
use jacquard_identity::{PublicResolver, resolver::IdentityResolver};
18
+
use josekit::jwe::alg::direct::DirectJweAlgorithm;
19
+
use lettre::{
20
+
AsyncTransport, Message,
21
+
message::{MultiPart, SinglePart, header},
22
+
};
13
23
use rand::Rng;
14
24
use serde::de::DeserializeOwned;
15
25
use serde_json::{Map, Value};
16
26
use sha2::{Digest, Sha256};
17
27
use sqlx::SqlitePool;
28
+
use std::sync::Arc;
18
29
use tracing::{error, log};
19
30
20
31
///Used to generate the email 2fa code
···
39
50
where
40
51
T: DeserializeOwned,
41
52
{
42
-
let uri = format!("{}{}", state.pds_base_url, path);
53
+
let uri = format!("{}{}", state.app_config.pds_base_url, path);
43
54
*req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?;
44
55
45
56
let result = state
···
134
145
full_code.push(UPPERCASE_BASE32_CHARS[idx] as char);
135
146
}
136
147
137
-
//The PDS implementation creates in lowercase, then converts to uppercase.
138
-
//Just going a head and doing uppercase here.
139
-
let slice_one = &full_code[0..5].to_ascii_uppercase();
140
-
let slice_two = &full_code[5..10].to_ascii_uppercase();
148
+
let slice_one = &full_code[0..5];
149
+
let slice_two = &full_code[5..10];
141
150
format!("{slice_one}-{slice_two}")
142
151
}
143
152
···
337
346
338
347
let email_message = Message::builder()
339
348
//TODO prob get the proper type in the state
340
-
.from(state.mailer_from.parse()?)
349
+
.from(state.app_config.mailer_from.parse()?)
341
350
.to(email.parse()?)
342
-
.subject("Sign in to Bluesky")
351
+
.subject(&state.app_config.email_subject)
343
352
.multipart(
344
353
MultiPart::alternative() // This is composed of two parts.
345
354
.singlepart(
···
522
531
523
532
format!("{masked_local}@{masked_domain}")
524
533
}
534
+
535
+
pub enum VerifyServiceAuthError {
536
+
AuthFailed,
537
+
Error(anyhow::Error),
538
+
}
539
+
540
+
/// Verifies the service auth token that is appended to an XRPC proxy request
541
+
pub async fn verify_service_auth(
542
+
jwt: &str,
543
+
lxm: &Nsid<'static>,
544
+
public_resolver: Arc<PublicResolver>,
545
+
service_did: &Did<'static>,
546
+
//The did of the user wanting to create an account
547
+
requested_did: &Did<'static>,
548
+
) -> Result<(), VerifyServiceAuthError> {
549
+
let parsed =
550
+
service_auth::parse_jwt(jwt).map_err(|e| VerifyServiceAuthError::Error(e.into()))?;
551
+
552
+
let claims = parsed.claims();
553
+
554
+
let did_doc = public_resolver
555
+
.resolve_did_doc(&requested_did)
556
+
.await
557
+
.map_err(|err| {
558
+
log::error!("Error resolving the service auth for: {}", claims.iss);
559
+
return VerifyServiceAuthError::Error(err.into());
560
+
})?;
561
+
562
+
// Parse the DID document response to get verification methods
563
+
let doc = did_doc.parse().map_err(|err| {
564
+
log::error!("Error parsing the service auth did doc: {}", claims.iss);
565
+
VerifyServiceAuthError::Error(anyhow::anyhow!(err))
566
+
})?;
567
+
568
+
let verification_methods = doc.verification_method.as_deref().ok_or_else(|| {
569
+
VerifyServiceAuthError::Error(anyhow::anyhow!(
570
+
"No verification methods in did doc: {}",
571
+
&claims.iss
572
+
))
573
+
})?;
574
+
575
+
let signing_key = extract_signing_key(verification_methods).ok_or_else(|| {
576
+
VerifyServiceAuthError::Error(anyhow::anyhow!(
577
+
"No signing key found in did doc: {}",
578
+
&claims.iss
579
+
))
580
+
})?;
581
+
582
+
service_auth::verify_signature(&parsed, &signing_key).map_err(|err| {
583
+
log::error!("Error verifying service auth signature: {}", err);
584
+
VerifyServiceAuthError::AuthFailed
585
+
})?;
586
+
587
+
// Now validate claims (audience, expiration, etc.)
588
+
claims.validate(service_did).map_err(|e| {
589
+
log::error!("Error validating service auth claims: {}", e);
590
+
VerifyServiceAuthError::AuthFailed
591
+
})?;
592
+
593
+
if claims.aud != *service_did {
594
+
log::error!("Invalid audience (did:web): {}", claims.aud);
595
+
return Err(VerifyServiceAuthError::AuthFailed);
596
+
}
597
+
598
+
let lxm_from_claims = claims.lxm.as_ref().ok_or_else(|| {
599
+
VerifyServiceAuthError::Error(anyhow::anyhow!("No lxm claim in service auth JWT"))
600
+
})?;
601
+
602
+
if lxm_from_claims != lxm {
603
+
return Err(VerifyServiceAuthError::Error(anyhow::anyhow!(
604
+
"Invalid XRPC endpoint requested"
605
+
)));
606
+
}
607
+
Ok(())
608
+
}
609
+
610
+
/// Ripped from Jacquard
611
+
///
612
+
/// Extract the signing key from a DID document's verification methods.
613
+
///
614
+
/// This looks for a key with type "atproto" or the first available key
615
+
/// if no atproto-specific key is found.
616
+
fn extract_signing_key(methods: &[VerificationMethod]) -> Option<PublicKey> {
617
+
// First try to find an atproto-specific key
618
+
let atproto_method = methods
619
+
.iter()
620
+
.find(|m| m.r#type.as_ref() == "Multikey" || m.r#type.as_ref() == "atproto");
621
+
622
+
let method = atproto_method.or_else(|| methods.first())?;
623
+
624
+
// Parse the multikey
625
+
let public_key_multibase = method.public_key_multibase.as_ref()?;
626
+
627
+
// Decode multibase
628
+
let (_, key_bytes) = multibase::decode(public_key_multibase.as_ref()).ok()?;
629
+
630
+
// First two bytes are the multicodec prefix
631
+
if key_bytes.len() < 2 {
632
+
return None;
633
+
}
634
+
635
+
let codec = &key_bytes[..2];
636
+
let key_material = &key_bytes[2..];
637
+
638
+
match codec {
639
+
// p256-pub (0x1200)
640
+
[0x80, 0x24] => PublicKey::from_p256_bytes(key_material).ok(),
641
+
// secp256k1-pub (0xe7)
642
+
[0xe7, 0x01] => PublicKey::from_k256_bytes(key_material).ok(),
643
+
_ => None,
644
+
}
645
+
}
646
+
647
+
/// Payload for gate JWE tokens
648
+
#[derive(serde::Serialize, serde::Deserialize, Debug)]
649
+
pub struct GateTokenPayload {
650
+
pub handle: String,
651
+
pub created_at: String,
652
+
}
653
+
654
+
/// Generate a secure JWE token for gate verification
655
+
pub fn generate_gate_token(handle: &str, encryption_key: &[u8]) -> Result<String, anyhow::Error> {
656
+
use josekit::jwe::{JweHeader, alg::direct::DirectJweAlgorithm};
657
+
658
+
let payload = GateTokenPayload {
659
+
handle: handle.to_string(),
660
+
created_at: Utc::now().to_rfc3339(),
661
+
};
662
+
663
+
let payload_json = serde_json::to_string(&payload)?;
664
+
665
+
let mut header = JweHeader::new();
666
+
header.set_token_type("JWT");
667
+
header.set_content_encryption("A128CBC-HS256");
668
+
669
+
let encrypter = DirectJweAlgorithm::Dir.encrypter_from_bytes(encryption_key)?;
670
+
671
+
// Encrypt
672
+
let jwe = josekit::jwe::serialize_compact(payload_json.as_bytes(), &header, &encrypter)?;
673
+
674
+
Ok(jwe)
675
+
}
676
+
677
+
/// Verify and decrypt a gate JWE token, returning the payload if valid
678
+
pub fn verify_gate_token(
679
+
token: &str,
680
+
encryption_key: &[u8],
681
+
) -> Result<GateTokenPayload, anyhow::Error> {
682
+
let decrypter = DirectJweAlgorithm::Dir.decrypter_from_bytes(encryption_key)?;
683
+
let (payload_bytes, _header) = josekit::jwe::deserialize_compact(token, &decrypter)?;
684
+
let payload: GateTokenPayload = serde_json::from_slice(&payload_bytes)?;
685
+
686
+
Ok(payload)
687
+
}
+206
-33
src/main.rs
+206
-33
src/main.rs
···
1
1
#![warn(clippy::unwrap_used)]
2
+
use crate::gate::{get_gate, post_gate};
2
3
use crate::oauth_provider::sign_in;
3
-
use crate::xrpc::com_atproto_server::{create_session, get_session, update_email};
4
-
use axum::body::Body;
5
-
use axum::handler::Handler;
6
-
use axum::http::{Method, header};
7
-
use axum::middleware as ax_middleware;
8
-
use axum::routing::post;
9
-
use axum::{Router, routing::get};
4
+
use crate::xrpc::com_atproto_server::{
5
+
create_account, create_session, describe_server, get_session, update_email,
6
+
};
7
+
use axum::{
8
+
Router,
9
+
body::Body,
10
+
handler::Handler,
11
+
http::{Method, header},
12
+
middleware as ax_middleware,
13
+
routing::get,
14
+
routing::post,
15
+
};
10
16
use axum_template::engine::Engine;
11
17
use handlebars::Handlebars;
12
-
use hyper_util::client::legacy::connect::HttpConnector;
13
-
use hyper_util::rt::TokioExecutor;
18
+
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
19
+
use jacquard_common::types::did::Did;
20
+
use jacquard_identity::{PublicResolver, resolver::PlcSource};
14
21
use lettre::{AsyncSmtpTransport, Tokio1Executor};
22
+
use rand::Rng;
15
23
use rust_embed::RustEmbed;
16
24
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
17
25
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
18
26
use std::path::Path;
27
+
use std::sync::Arc;
19
28
use std::time::Duration;
20
29
use std::{env, net::SocketAddr};
21
-
use tower_governor::GovernorLayer;
22
-
use tower_governor::governor::GovernorConfigBuilder;
23
-
use tower_http::compression::CompressionLayer;
24
-
use tower_http::cors::{Any, CorsLayer};
30
+
use tower_governor::{
31
+
GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor,
32
+
};
33
+
use tower_http::{
34
+
compression::CompressionLayer,
35
+
cors::{Any, CorsLayer},
36
+
};
25
37
use tracing::log;
26
38
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
27
39
40
+
mod gate;
28
41
pub mod helpers;
29
42
mod middleware;
30
43
mod oauth_provider;
···
37
50
#[include = "*.hbs"]
38
51
struct EmailTemplates;
39
52
53
+
#[derive(RustEmbed)]
54
+
#[folder = "html_templates"]
55
+
#[include = "*.hbs"]
56
+
struct HtmlTemplates;
57
+
58
+
/// Mostly the env variables that are used in the app
59
+
#[derive(Clone, Debug)]
60
+
pub struct AppConfig {
61
+
pds_base_url: String,
62
+
mailer_from: String,
63
+
email_subject: String,
64
+
allow_only_migrations: bool,
65
+
use_captcha: bool,
66
+
//The url to redirect to after a successful captcha. Defaults to https://bsky.app, but you may have another social-app fork you rather your users use
67
+
//that need to capture this redirect url for creating an account
68
+
default_successful_redirect_url: String,
69
+
pds_service_did: Did<'static>,
70
+
gate_jwe_key: Vec<u8>,
71
+
captcha_success_redirects: Vec<String>,
72
+
}
73
+
74
+
impl AppConfig {
75
+
pub fn new() -> Self {
76
+
let pds_base_url =
77
+
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
78
+
let mailer_from = env::var("PDS_EMAIL_FROM_ADDRESS")
79
+
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
80
+
//Hack not my favorite, but it does work
81
+
let allow_only_migrations = env::var("GATEKEEPER_ALLOW_ONLY_MIGRATIONS")
82
+
.map(|val| val.parse::<bool>().unwrap_or(false))
83
+
.unwrap_or(false);
84
+
85
+
let use_captcha = env::var("GATEKEEPER_CREATE_ACCOUNT_CAPTCHA")
86
+
.map(|val| val.parse::<bool>().unwrap_or(false))
87
+
.unwrap_or(false);
88
+
89
+
// PDS_SERVICE_DID is the did:web if set, if not it's PDS_HOSTNAME
90
+
let pds_service_did =
91
+
env::var("PDS_SERVICE_DID").unwrap_or_else(|_| match env::var("PDS_HOSTNAME") {
92
+
Ok(pds_hostname) => format!("did:web:{}", pds_hostname),
93
+
Err(_) => {
94
+
panic!("PDS_HOSTNAME or PDS_SERVICE_DID must be set in your pds.env file")
95
+
}
96
+
});
97
+
98
+
let email_subject = env::var("GATEKEEPER_TWO_FACTOR_EMAIL_SUBJECT")
99
+
.unwrap_or("Sign in to Bluesky".to_string());
100
+
101
+
// Load or generate JWE encryption key (32 bytes for AES-256)
102
+
let gate_jwe_key = env::var("GATEKEEPER_JWE_KEY")
103
+
.ok()
104
+
.and_then(|key_hex| hex::decode(key_hex).ok())
105
+
.unwrap_or_else(|| {
106
+
// Generate a random 32-byte key if not provided
107
+
let key: Vec<u8> = (0..32).map(|_| rand::rng().random()).collect();
108
+
log::warn!("WARNING: No GATEKEEPER_JWE_KEY found in the environment. Generated random key (hex): {}", hex::encode(&key));
109
+
log::warn!("This is not strictly needed unless you scale PDS Gatekeeper. Will not also be able to verify tokens between reboots, but they are short lived (5mins).");
110
+
key
111
+
});
112
+
113
+
if gate_jwe_key.len() != 32 {
114
+
panic!(
115
+
"GATEKEEPER_JWE_KEY must be 32 bytes (64 hex characters) for AES-256 encryption"
116
+
);
117
+
}
118
+
119
+
let captcha_success_redirects = match env::var("GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS") {
120
+
Ok(from_env) => from_env.split(",").map(|s| s.trim().to_string()).collect(),
121
+
Err(_) => {
122
+
vec![
123
+
String::from("https://bsky.app"),
124
+
String::from("https://pdsmoover.com"),
125
+
String::from("https://blacksky.community"),
126
+
String::from("https://tektite.cc"),
127
+
]
128
+
}
129
+
};
130
+
131
+
AppConfig {
132
+
pds_base_url,
133
+
mailer_from,
134
+
email_subject,
135
+
allow_only_migrations,
136
+
use_captcha,
137
+
default_successful_redirect_url: env::var("GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT")
138
+
.unwrap_or("https://bsky.app".to_string()),
139
+
pds_service_did: pds_service_did
140
+
.parse()
141
+
.expect("PDS_SERVICE_DID is not a valid did or could not infer from PDS_HOSTNAME"),
142
+
gate_jwe_key,
143
+
captcha_success_redirects,
144
+
}
145
+
}
146
+
}
147
+
40
148
#[derive(Clone)]
41
149
pub struct AppState {
42
150
account_pool: SqlitePool,
43
151
pds_gatekeeper_pool: SqlitePool,
44
152
reverse_proxy_client: HyperUtilClient,
45
-
pds_base_url: String,
46
153
mailer: AsyncSmtpTransport<Tokio1Executor>,
47
-
mailer_from: String,
48
154
template_engine: Engine<Handlebars<'static>>,
155
+
resolver: Arc<PublicResolver>,
156
+
app_config: AppConfig,
49
157
}
50
158
51
159
async fn root_handler() -> impl axum::response::IntoResponse {
···
91
199
let pds_env_location =
92
200
env::var("PDS_ENV_LOCATION").unwrap_or_else(|_| "/pds/pds.env".to_string());
93
201
94
-
dotenvy::from_path(Path::new(&pds_env_location))?;
95
-
let pds_root = env::var("PDS_DATA_DIRECTORY")?;
202
+
let result_of_finding_pds_env = dotenvy::from_path(Path::new(&pds_env_location));
203
+
if let Err(e) = result_of_finding_pds_env {
204
+
log::error!(
205
+
"Error loading pds.env file (ignore if you loaded your variables in the environment somehow else): {e}"
206
+
);
207
+
}
208
+
209
+
let pds_root =
210
+
env::var("PDS_DATA_DIRECTORY").expect("PDS_DATA_DIRECTORY is not set in your pds.env file");
96
211
let account_db_url = format!("{pds_root}/account.sqlite");
97
212
98
213
let account_options = SqliteConnectOptions::new()
···
129
244
//Emailer set up
130
245
let smtp_url =
131
246
env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file");
132
-
let sent_from = env::var("PDS_EMAIL_FROM_ADDRESS")
133
-
.expect("PDS_EMAIL_FROM_ADDRESS is not set in your pds.env file");
134
247
135
248
let mailer: AsyncSmtpTransport<Tokio1Executor> =
136
249
AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build();
···
147
260
let _ = hbs.register_embed_templates::<EmailTemplates>();
148
261
}
149
262
150
-
let pds_base_url =
151
-
env::var("PDS_BASE_URL").unwrap_or_else(|_| "http://localhost:3000".to_string());
263
+
let _ = hbs.register_embed_templates::<HtmlTemplates>();
264
+
265
+
//Reads the PLC source from the pds env's or defaults to ol faithful
266
+
let plc_source_url =
267
+
env::var("PDS_DID_PLC_URL").unwrap_or_else(|_| "https://plc.directory".to_string());
268
+
let plc_source = PlcSource::PlcDirectory {
269
+
base: plc_source_url.parse().unwrap(),
270
+
};
271
+
let mut resolver = PublicResolver::default();
272
+
resolver = resolver.with_plc_source(plc_source.clone());
152
273
153
274
let state = AppState {
154
275
account_pool,
155
276
pds_gatekeeper_pool,
156
277
reverse_proxy_client: client,
157
-
pds_base_url,
158
278
mailer,
159
-
mailer_from: sent_from,
160
279
template_engine: Engine::from(hbs),
280
+
resolver: Arc::new(resolver),
281
+
app_config: AppConfig::new(),
161
282
};
162
283
163
284
// Rate limiting
164
285
//Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds.
165
-
let create_session_governor_conf = GovernorConfigBuilder::default()
286
+
let captcha_governor_conf = GovernorConfigBuilder::default()
166
287
.per_second(60)
167
288
.burst_size(5)
289
+
.key_extractor(SmartIpKeyExtractor)
168
290
.finish()
169
-
.expect("failed to create governor config. this should not happen and is a bug");
291
+
.expect("failed to create governor config for create session. this should not happen and is a bug");
170
292
171
293
// Create a second config with the same settings for the other endpoint
172
294
let sign_in_governor_conf = GovernorConfigBuilder::default()
173
295
.per_second(60)
174
296
.burst_size(5)
297
+
.key_extractor(SmartIpKeyExtractor)
175
298
.finish()
176
-
.expect("failed to create governor config. this should not happen and is a bug");
299
+
.expect(
300
+
"failed to create governor config for sign in. this should not happen and is a bug",
301
+
);
302
+
303
+
let create_account_limiter_time: Option<String> =
304
+
env::var("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND").ok();
305
+
let create_account_limiter_burst: Option<String> =
306
+
env::var("GATEKEEPER_CREATE_ACCOUNT_BURST").ok();
307
+
308
+
//Default should be 608 requests per 5 minutes, PDS is 300 per 500 so will never hit it ideally
309
+
let mut create_account_governor_conf = GovernorConfigBuilder::default();
310
+
if create_account_limiter_time.is_some() {
311
+
let time = create_account_limiter_time
312
+
.expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND not set")
313
+
.parse::<u64>()
314
+
.expect("GATEKEEPER_CREATE_ACCOUNT_PER_SECOND must be a valid integer");
315
+
create_account_governor_conf.per_second(time);
316
+
}
177
317
178
-
let create_session_governor_limiter = create_session_governor_conf.limiter().clone();
318
+
if create_account_limiter_burst.is_some() {
319
+
let burst = create_account_limiter_burst
320
+
.expect("GATEKEEPER_CREATE_ACCOUNT_BURST not set")
321
+
.parse::<u32>()
322
+
.expect("GATEKEEPER_CREATE_ACCOUNT_BURST must be a valid integer");
323
+
create_account_governor_conf.burst_size(burst);
324
+
}
325
+
326
+
let create_account_governor_conf = create_account_governor_conf
327
+
.key_extractor(SmartIpKeyExtractor)
328
+
.finish().expect(
329
+
"failed to create governor config for create account. this should not happen and is a bug",
330
+
);
331
+
332
+
let captcha_governor_limiter = captcha_governor_conf.limiter().clone();
179
333
let sign_in_governor_limiter = sign_in_governor_conf.limiter().clone();
334
+
let create_account_governor_limiter = create_account_governor_conf.limiter().clone();
335
+
336
+
let sign_in_governor_layer = GovernorLayer::new(sign_in_governor_conf);
337
+
180
338
let interval = Duration::from_secs(60);
181
339
// a separate background task to clean up
182
340
std::thread::spawn(move || {
183
341
loop {
184
342
std::thread::sleep(interval);
185
-
create_session_governor_limiter.retain_recent();
343
+
captcha_governor_limiter.retain_recent();
186
344
sign_in_governor_limiter.retain_recent();
345
+
create_account_governor_limiter.retain_recent();
187
346
}
188
347
});
189
348
···
192
351
.allow_methods([Method::GET, Method::OPTIONS, Method::POST])
193
352
.allow_headers(Any);
194
353
195
-
let app = Router::new()
354
+
let mut app = Router::new()
196
355
.route("/", get(root_handler))
356
+
.route("/xrpc/com.atproto.server.getSession", get(get_session))
197
357
.route(
198
-
"/xrpc/com.atproto.server.getSession",
199
-
get(get_session).layer(ax_middleware::from_fn(middleware::extract_did)),
358
+
"/xrpc/com.atproto.server.describeServer",
359
+
get(describe_server),
200
360
)
201
361
.route(
202
362
"/xrpc/com.atproto.server.updateEmail",
···
204
364
)
205
365
.route(
206
366
"/@atproto/oauth-provider/~api/sign-in",
207
-
post(sign_in).layer(GovernorLayer::new(sign_in_governor_conf)),
367
+
post(sign_in).layer(sign_in_governor_layer.clone()),
208
368
)
209
369
.route(
210
370
"/xrpc/com.atproto.server.createSession",
211
-
post(create_session.layer(GovernorLayer::new(create_session_governor_conf))),
371
+
post(create_session.layer(sign_in_governor_layer)),
212
372
)
373
+
.route(
374
+
"/xrpc/com.atproto.server.createAccount",
375
+
post(create_account).layer(GovernorLayer::new(create_account_governor_conf)),
376
+
);
377
+
378
+
if state.app_config.use_captcha {
379
+
app = app.route(
380
+
"/gate/signup",
381
+
get(get_gate).post(post_gate.layer(GovernorLayer::new(captcha_governor_conf))),
382
+
);
383
+
}
384
+
385
+
let app = app
213
386
.layer(CompressionLayer::new())
214
387
.layer(cors)
215
388
.with_state(state);
+73
-39
src/middleware.rs
+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
}
+3
-2
src/oauth_provider.rs
+3
-2
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>,
···
56
57
//No 2FA or already passed
57
58
let uri = format!(
58
59
"{}{}",
59
-
state.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
60
+
state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
60
61
);
61
62
62
63
let mut req = axum::http::Request::post(uri);
+394
-53
src/xrpc/com_atproto_server.rs
+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
+
}