+2703
-217
Cargo.lock
+2703
-217
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"
···
24
43
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
25
44
dependencies = [
26
45
"cfg-if",
27
-
"getrandom 0.3.3",
28
46
"once_cell",
29
47
"version_check",
30
48
"zerocopy",
···
40
58
]
41
59
42
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]]
43
67
name = "allocator-api2"
44
68
version = "0.2.21"
45
69
source = "registry+https://github.com/rust-lang/crates.io-index"
46
70
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
47
71
48
72
[[package]]
73
+
name = "android_system_properties"
74
+
version = "0.1.5"
75
+
source = "registry+https://github.com/rust-lang/crates.io-index"
76
+
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
77
+
dependencies = [
78
+
"libc",
79
+
]
80
+
81
+
[[package]]
82
+
name = "anyhow"
83
+
version = "1.0.99"
84
+
source = "registry+https://github.com/rust-lang/crates.io-index"
85
+
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
86
+
87
+
[[package]]
88
+
name = "async-compression"
89
+
version = "0.4.27"
90
+
source = "registry+https://github.com/rust-lang/crates.io-index"
91
+
checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8"
92
+
dependencies = [
93
+
"flate2",
94
+
"futures-core",
95
+
"memchr",
96
+
"pin-project-lite",
97
+
"tokio",
98
+
"zstd",
99
+
"zstd-safe",
100
+
]
101
+
102
+
[[package]]
49
103
name = "async-trait"
50
104
version = "0.1.89"
51
105
source = "registry+https://github.com/rust-lang/crates.io-index"
···
66
120
]
67
121
68
122
[[package]]
123
+
name = "atomic-waker"
124
+
version = "1.1.2"
125
+
source = "registry+https://github.com/rust-lang/crates.io-index"
126
+
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
127
+
128
+
[[package]]
69
129
name = "autocfg"
70
130
version = "1.5.0"
71
131
source = "registry+https://github.com/rust-lang/crates.io-index"
72
132
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
73
133
74
134
[[package]]
135
+
name = "aws-lc-rs"
136
+
version = "1.13.3"
137
+
source = "registry+https://github.com/rust-lang/crates.io-index"
138
+
checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
139
+
dependencies = [
140
+
"aws-lc-sys",
141
+
"untrusted 0.7.1",
142
+
"zeroize",
143
+
]
144
+
145
+
[[package]]
146
+
name = "aws-lc-sys"
147
+
version = "0.30.0"
148
+
source = "registry+https://github.com/rust-lang/crates.io-index"
149
+
checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
150
+
dependencies = [
151
+
"bindgen",
152
+
"cc",
153
+
"cmake",
154
+
"dunce",
155
+
"fs_extra",
156
+
]
157
+
158
+
[[package]]
75
159
name = "axum"
76
-
version = "0.7.9"
160
+
version = "0.8.4"
77
161
source = "registry+https://github.com/rust-lang/crates.io-index"
78
-
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
162
+
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
79
163
dependencies = [
80
-
"async-trait",
81
164
"axum-core",
82
165
"axum-macros",
83
166
"bytes",
167
+
"form_urlencoded",
84
168
"futures-util",
85
169
"http",
86
170
"http-body",
···
108
192
109
193
[[package]]
110
194
name = "axum-core"
111
-
version = "0.4.5"
195
+
version = "0.5.2"
112
196
source = "registry+https://github.com/rust-lang/crates.io-index"
113
-
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
197
+
checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6"
114
198
dependencies = [
115
-
"async-trait",
116
199
"bytes",
117
-
"futures-util",
200
+
"futures-core",
118
201
"http",
119
202
"http-body",
120
203
"http-body-util",
···
129
212
130
213
[[package]]
131
214
name = "axum-macros"
132
-
version = "0.4.2"
215
+
version = "0.5.0"
133
216
source = "registry+https://github.com/rust-lang/crates.io-index"
134
-
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
217
+
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
135
218
dependencies = [
136
219
"proc-macro2",
137
220
"quote",
138
221
"syn 2.0.105",
222
+
]
223
+
224
+
[[package]]
225
+
name = "axum-template"
226
+
version = "3.0.0"
227
+
source = "registry+https://github.com/rust-lang/crates.io-index"
228
+
checksum = "3df50f7d669bfc3a8c348f08f536fe37e7acfbeded3cfdffd2ad3d76725fc40c"
229
+
dependencies = [
230
+
"axum",
231
+
"handlebars",
232
+
"serde",
233
+
"thiserror 2.0.14",
139
234
]
140
235
141
236
[[package]]
···
154
249
]
155
250
156
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]]
157
274
name = "base64"
158
-
version = "0.21.7"
275
+
version = "0.22.1"
159
276
source = "registry+https://github.com/rust-lang/crates.io-index"
160
-
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
277
+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
161
278
162
279
[[package]]
163
280
name = "base64ct"
···
166
283
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
167
284
168
285
[[package]]
286
+
name = "bindgen"
287
+
version = "0.69.5"
288
+
source = "registry+https://github.com/rust-lang/crates.io-index"
289
+
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
290
+
dependencies = [
291
+
"bitflags",
292
+
"cexpr",
293
+
"clang-sys",
294
+
"itertools",
295
+
"lazy_static",
296
+
"lazycell",
297
+
"log",
298
+
"prettyplease",
299
+
"proc-macro2",
300
+
"quote",
301
+
"regex",
302
+
"rustc-hash 1.1.0",
303
+
"shlex",
304
+
"syn 2.0.105",
305
+
"which",
306
+
]
307
+
308
+
[[package]]
169
309
name = "bitflags"
170
310
version = "2.9.1"
171
311
source = "registry+https://github.com/rust-lang/crates.io-index"
···
184
324
]
185
325
186
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]]
361
+
name = "bstr"
362
+
version = "1.12.0"
363
+
source = "registry+https://github.com/rust-lang/crates.io-index"
364
+
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
365
+
dependencies = [
366
+
"memchr",
367
+
"serde",
368
+
]
369
+
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]]
395
+
name = "bumpalo"
396
+
version = "3.19.0"
397
+
source = "registry+https://github.com/rust-lang/crates.io-index"
398
+
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
399
+
400
+
[[package]]
187
401
name = "byteorder"
188
402
version = "1.5.0"
189
403
source = "registry+https://github.com/rust-lang/crates.io-index"
···
194
408
version = "1.10.1"
195
409
source = "registry+https://github.com/rust-lang/crates.io-index"
196
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
+
]
197
423
198
424
[[package]]
199
425
name = "cc"
···
201
427
source = "registry+https://github.com/rust-lang/crates.io-index"
202
428
checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e"
203
429
dependencies = [
430
+
"jobserver",
431
+
"libc",
204
432
"shlex",
205
433
]
206
434
207
435
[[package]]
436
+
name = "cc-traits"
437
+
version = "2.0.0"
438
+
source = "registry+https://github.com/rust-lang/crates.io-index"
439
+
checksum = "060303ef31ef4a522737e1b1ab68c67916f2a787bb2f4f54f383279adba962b5"
440
+
dependencies = [
441
+
"slab",
442
+
]
443
+
444
+
[[package]]
445
+
name = "cexpr"
446
+
version = "0.6.0"
447
+
source = "registry+https://github.com/rust-lang/crates.io-index"
448
+
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
449
+
dependencies = [
450
+
"nom 7.1.3",
451
+
]
452
+
453
+
[[package]]
208
454
name = "cfg-if"
209
455
version = "1.0.1"
210
456
source = "registry+https://github.com/rust-lang/crates.io-index"
211
457
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
212
458
213
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]]
466
+
name = "chrono"
467
+
version = "0.4.42"
468
+
source = "registry+https://github.com/rust-lang/crates.io-index"
469
+
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
470
+
dependencies = [
471
+
"iana-time-zone",
472
+
"js-sys",
473
+
"num-traits",
474
+
"serde",
475
+
"wasm-bindgen",
476
+
"windows-link 0.2.1",
477
+
]
478
+
479
+
[[package]]
480
+
name = "chumsky"
481
+
version = "0.9.3"
482
+
source = "registry+https://github.com/rust-lang/crates.io-index"
483
+
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
484
+
dependencies = [
485
+
"hashbrown 0.14.5",
486
+
"stacker",
487
+
]
488
+
489
+
[[package]]
490
+
name = "ciborium"
491
+
version = "0.2.2"
492
+
source = "registry+https://github.com/rust-lang/crates.io-index"
493
+
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
494
+
dependencies = [
495
+
"ciborium-io",
496
+
"ciborium-ll",
497
+
"serde",
498
+
]
499
+
500
+
[[package]]
501
+
name = "ciborium-io"
502
+
version = "0.2.2"
503
+
source = "registry+https://github.com/rust-lang/crates.io-index"
504
+
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
505
+
506
+
[[package]]
507
+
name = "ciborium-ll"
508
+
version = "0.2.2"
509
+
source = "registry+https://github.com/rust-lang/crates.io-index"
510
+
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
511
+
dependencies = [
512
+
"ciborium-io",
513
+
"half",
514
+
]
515
+
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]]
531
+
name = "cipher"
532
+
version = "0.4.4"
533
+
source = "registry+https://github.com/rust-lang/crates.io-index"
534
+
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
535
+
dependencies = [
536
+
"crypto-common",
537
+
"inout",
538
+
]
539
+
540
+
[[package]]
541
+
name = "clang-sys"
542
+
version = "1.8.1"
543
+
source = "registry+https://github.com/rust-lang/crates.io-index"
544
+
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
545
+
dependencies = [
546
+
"glob",
547
+
"libc",
548
+
"libloading",
549
+
]
550
+
551
+
[[package]]
552
+
name = "cmake"
553
+
version = "0.1.54"
554
+
source = "registry+https://github.com/rust-lang/crates.io-index"
555
+
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
556
+
dependencies = [
557
+
"cc",
558
+
]
559
+
560
+
[[package]]
561
+
name = "concurrent-queue"
562
+
version = "2.5.0"
563
+
source = "registry+https://github.com/rust-lang/crates.io-index"
564
+
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
565
+
dependencies = [
566
+
"crossbeam-utils",
567
+
]
568
+
569
+
[[package]]
214
570
name = "const-oid"
215
571
version = "0.9.6"
216
572
source = "registry+https://github.com/rust-lang/crates.io-index"
217
573
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
574
+
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]]
592
+
name = "core-foundation-sys"
593
+
version = "0.8.7"
594
+
source = "registry+https://github.com/rust-lang/crates.io-index"
595
+
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
596
+
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
+
]
218
605
219
606
[[package]]
220
607
name = "cpufeatures"
···
241
628
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
242
629
243
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]]
244
640
name = "crossbeam-queue"
245
641
version = "0.3.12"
246
642
source = "registry+https://github.com/rust-lang/crates.io-index"
···
256
652
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
257
653
258
654
[[package]]
655
+
name = "crunchy"
656
+
version = "0.2.4"
657
+
source = "registry+https://github.com/rust-lang/crates.io-index"
658
+
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
659
+
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]]
259
673
name = "crypto-common"
260
674
version = "0.1.6"
261
675
source = "registry+https://github.com/rust-lang/crates.io-index"
···
263
677
dependencies = [
264
678
"generic-array",
265
679
"typenum",
680
+
]
681
+
682
+
[[package]]
683
+
name = "darling"
684
+
version = "0.20.11"
685
+
source = "registry+https://github.com/rust-lang/crates.io-index"
686
+
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
687
+
dependencies = [
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",
700
+
]
701
+
702
+
[[package]]
703
+
name = "darling_core"
704
+
version = "0.20.11"
705
+
source = "registry+https://github.com/rust-lang/crates.io-index"
706
+
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
707
+
dependencies = [
708
+
"fnv",
709
+
"ident_case",
710
+
"proc-macro2",
711
+
"quote",
712
+
"strsim",
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",
728
+
]
729
+
730
+
[[package]]
731
+
name = "darling_macro"
732
+
version = "0.20.11"
733
+
source = "registry+https://github.com/rust-lang/crates.io-index"
734
+
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
735
+
dependencies = [
736
+
"darling_core 0.20.11",
737
+
"quote",
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",
750
+
]
751
+
752
+
[[package]]
753
+
name = "dashmap"
754
+
version = "6.1.0"
755
+
source = "registry+https://github.com/rust-lang/crates.io-index"
756
+
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
757
+
dependencies = [
758
+
"cfg-if",
759
+
"crossbeam-utils",
760
+
"hashbrown 0.14.5",
761
+
"lock_api",
762
+
"once_cell",
763
+
"parking_lot_core",
764
+
]
765
+
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",
266
790
]
267
791
268
792
[[package]]
···
277
801
]
278
802
279
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]]
814
+
name = "derive_builder"
815
+
version = "0.20.2"
816
+
source = "registry+https://github.com/rust-lang/crates.io-index"
817
+
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
818
+
dependencies = [
819
+
"derive_builder_macro",
820
+
]
821
+
822
+
[[package]]
823
+
name = "derive_builder_core"
824
+
version = "0.20.2"
825
+
source = "registry+https://github.com/rust-lang/crates.io-index"
826
+
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
827
+
dependencies = [
828
+
"darling 0.20.11",
829
+
"proc-macro2",
830
+
"quote",
831
+
"syn 2.0.105",
832
+
]
833
+
834
+
[[package]]
835
+
name = "derive_builder_macro"
836
+
version = "0.20.2"
837
+
source = "registry+https://github.com/rust-lang/crates.io-index"
838
+
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
839
+
dependencies = [
840
+
"derive_builder_core",
841
+
"syn 2.0.105",
842
+
]
843
+
844
+
[[package]]
280
845
name = "digest"
281
846
version = "0.10.7"
282
847
source = "registry+https://github.com/rust-lang/crates.io-index"
···
306
871
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
307
872
308
873
[[package]]
874
+
name = "dunce"
875
+
version = "1.0.5"
876
+
source = "registry+https://github.com/rust-lang/crates.io-index"
877
+
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
878
+
879
+
[[package]]
880
+
name = "dyn-clone"
881
+
version = "1.0.20"
882
+
source = "registry+https://github.com/rust-lang/crates.io-index"
883
+
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
884
+
885
+
[[package]]
886
+
name = "ecdsa"
887
+
version = "0.16.9"
888
+
source = "registry+https://github.com/rust-lang/crates.io-index"
889
+
checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
890
+
dependencies = [
891
+
"der",
892
+
"digest",
893
+
"elliptic-curve",
894
+
"rfc6979",
895
+
"signature",
896
+
"spki",
897
+
]
898
+
899
+
[[package]]
309
900
name = "either"
310
901
version = "1.15.0"
311
902
source = "registry+https://github.com/rust-lang/crates.io-index"
···
315
906
]
316
907
317
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]]
929
+
name = "email-encoding"
930
+
version = "0.4.1"
931
+
source = "registry+https://github.com/rust-lang/crates.io-index"
932
+
checksum = "9298e6504d9b9e780ed3f7dfd43a61be8cd0e09eb07f7706a945b0072b6670b6"
933
+
dependencies = [
934
+
"base64",
935
+
"memchr",
936
+
]
937
+
938
+
[[package]]
939
+
name = "email_address"
940
+
version = "0.2.9"
941
+
source = "registry+https://github.com/rust-lang/crates.io-index"
942
+
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
943
+
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]]
318
954
name = "equivalent"
319
955
version = "1.0.2"
320
956
source = "registry+https://github.com/rust-lang/crates.io-index"
···
327
963
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
328
964
dependencies = [
329
965
"libc",
330
-
"windows-sys 0.60.2",
966
+
"windows-sys 0.59.0",
331
967
]
332
968
333
969
[[package]]
···
343
979
344
980
[[package]]
345
981
name = "event-listener"
346
-
version = "2.5.3"
982
+
version = "5.4.1"
347
983
source = "registry+https://github.com/rust-lang/crates.io-index"
348
-
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
984
+
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
985
+
dependencies = [
986
+
"concurrent-queue",
987
+
"parking",
988
+
"pin-project-lite",
989
+
]
349
990
350
991
[[package]]
351
992
name = "fastrand"
···
354
995
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
355
996
356
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]]
357
1018
name = "flume"
358
1019
version = "0.11.1"
359
1020
source = "registry+https://github.com/rust-lang/crates.io-index"
···
371
1032
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
372
1033
373
1034
[[package]]
1035
+
name = "foldhash"
1036
+
version = "0.1.5"
1037
+
source = "registry+https://github.com/rust-lang/crates.io-index"
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"
1054
+
1055
+
[[package]]
374
1056
name = "form_urlencoded"
375
1057
version = "1.2.1"
376
1058
source = "registry+https://github.com/rust-lang/crates.io-index"
···
378
1060
dependencies = [
379
1061
"percent-encoding",
380
1062
]
1063
+
1064
+
[[package]]
1065
+
name = "forwarded-header-value"
1066
+
version = "0.1.1"
1067
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1068
+
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
1069
+
dependencies = [
1070
+
"nonempty",
1071
+
"thiserror 1.0.69",
1072
+
]
1073
+
1074
+
[[package]]
1075
+
name = "fs_extra"
1076
+
version = "1.3.0"
1077
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1078
+
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
381
1079
382
1080
[[package]]
383
1081
name = "futures-channel"
···
424
1122
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
425
1123
426
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]]
427
1136
name = "futures-sink"
428
1137
version = "0.3.31"
429
1138
source = "registry+https://github.com/rust-lang/crates.io-index"
···
436
1145
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
437
1146
438
1147
[[package]]
1148
+
name = "futures-timer"
1149
+
version = "3.0.3"
1150
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1151
+
checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
1152
+
1153
+
[[package]]
439
1154
name = "futures-util"
440
1155
version = "0.3.31"
441
1156
source = "registry+https://github.com/rust-lang/crates.io-index"
···
443
1158
dependencies = [
444
1159
"futures-core",
445
1160
"futures-io",
1161
+
"futures-macro",
446
1162
"futures-sink",
447
1163
"futures-task",
448
1164
"memchr",
···
459
1175
dependencies = [
460
1176
"typenum",
461
1177
"version_check",
1178
+
"zeroize",
462
1179
]
463
1180
464
1181
[[package]]
···
468
1185
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
469
1186
dependencies = [
470
1187
"cfg-if",
1188
+
"js-sys",
471
1189
"libc",
472
-
"wasi 0.11.1+wasi-snapshot-preview1",
1190
+
"wasi",
1191
+
"wasm-bindgen",
473
1192
]
474
1193
475
1194
[[package]]
476
1195
name = "getrandom"
477
-
version = "0.3.3"
1196
+
version = "0.3.4"
478
1197
source = "registry+https://github.com/rust-lang/crates.io-index"
479
-
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
1198
+
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
480
1199
dependencies = [
481
1200
"cfg-if",
1201
+
"js-sys",
482
1202
"libc",
483
1203
"r-efi",
484
-
"wasi 0.14.2+wasi-0.2.4",
1204
+
"wasip2",
1205
+
"wasm-bindgen",
485
1206
]
486
1207
487
1208
[[package]]
···
491
1212
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
492
1213
493
1214
[[package]]
1215
+
name = "glob"
1216
+
version = "0.3.3"
1217
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1218
+
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
1219
+
1220
+
[[package]]
1221
+
name = "globset"
1222
+
version = "0.4.16"
1223
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1224
+
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
1225
+
dependencies = [
1226
+
"aho-corasick",
1227
+
"bstr",
1228
+
"log",
1229
+
"regex-automata 0.4.13",
1230
+
"regex-syntax 0.8.5",
1231
+
]
1232
+
1233
+
[[package]]
1234
+
name = "governor"
1235
+
version = "0.10.1"
1236
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1237
+
checksum = "444405bbb1a762387aa22dd569429533b54a1d8759d35d3b64cb39b0293eaa19"
1238
+
dependencies = [
1239
+
"cfg-if",
1240
+
"dashmap",
1241
+
"futures-sink",
1242
+
"futures-timer",
1243
+
"futures-util",
1244
+
"getrandom 0.3.4",
1245
+
"hashbrown 0.15.5",
1246
+
"nonzero_ext",
1247
+
"parking_lot",
1248
+
"portable-atomic",
1249
+
"quanta",
1250
+
"rand 0.9.2",
1251
+
"smallvec",
1252
+
"spinning_top",
1253
+
"web-time",
1254
+
]
1255
+
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]]
1268
+
name = "h2"
1269
+
version = "0.4.12"
1270
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1271
+
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
1272
+
dependencies = [
1273
+
"atomic-waker",
1274
+
"bytes",
1275
+
"fnv",
1276
+
"futures-core",
1277
+
"futures-sink",
1278
+
"http",
1279
+
"indexmap 2.10.0",
1280
+
"slab",
1281
+
"tokio",
1282
+
"tokio-util",
1283
+
"tracing",
1284
+
]
1285
+
1286
+
[[package]]
1287
+
name = "half"
1288
+
version = "2.6.0"
1289
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1290
+
checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
1291
+
dependencies = [
1292
+
"cfg-if",
1293
+
"crunchy",
1294
+
]
1295
+
1296
+
[[package]]
1297
+
name = "handlebars"
1298
+
version = "6.3.2"
1299
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1300
+
checksum = "759e2d5aea3287cb1190c8ec394f42866cb5bf74fcbf213f354e3c856ea26098"
1301
+
dependencies = [
1302
+
"derive_builder",
1303
+
"log",
1304
+
"num-order",
1305
+
"pest",
1306
+
"pest_derive",
1307
+
"rust-embed",
1308
+
"serde",
1309
+
"serde_json",
1310
+
"thiserror 2.0.14",
1311
+
]
1312
+
1313
+
[[package]]
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]]
494
1320
name = "hashbrown"
495
1321
version = "0.14.5"
496
1322
source = "registry+https://github.com/rust-lang/crates.io-index"
···
505
1331
version = "0.15.5"
506
1332
source = "registry+https://github.com/rust-lang/crates.io-index"
507
1333
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
1334
+
dependencies = [
1335
+
"allocator-api2",
1336
+
"equivalent",
1337
+
"foldhash",
1338
+
]
508
1339
509
1340
[[package]]
510
1341
name = "hashlink"
511
-
version = "0.8.4"
1342
+
version = "0.10.0"
512
1343
source = "registry+https://github.com/rust-lang/crates.io-index"
513
-
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
1344
+
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
514
1345
dependencies = [
515
-
"hashbrown 0.14.5",
1346
+
"hashbrown 0.15.5",
516
1347
]
517
1348
518
1349
[[package]]
···
520
1351
version = "0.4.1"
521
1352
source = "registry+https://github.com/rust-lang/crates.io-index"
522
1353
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
523
-
dependencies = [
524
-
"unicode-segmentation",
525
-
]
1354
+
1355
+
[[package]]
1356
+
name = "heck"
1357
+
version = "0.5.0"
1358
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1359
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
526
1360
527
1361
[[package]]
528
1362
name = "hex"
529
1363
version = "0.4.3"
530
1364
source = "registry+https://github.com/rust-lang/crates.io-index"
531
1365
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
1366
+
1367
+
[[package]]
1368
+
name = "hex_fmt"
1369
+
version = "0.3.0"
1370
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1371
+
checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f"
532
1372
533
1373
[[package]]
534
1374
name = "hkdf"
···
558
1398
]
559
1399
560
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",
1407
+
]
1408
+
1409
+
[[package]]
561
1410
name = "http"
562
1411
version = "1.3.1"
563
1412
source = "registry+https://github.com/rust-lang/crates.io-index"
···
612
1461
"bytes",
613
1462
"futures-channel",
614
1463
"futures-util",
1464
+
"h2",
615
1465
"http",
616
1466
"http-body",
617
1467
"httparse",
···
620
1470
"pin-project-lite",
621
1471
"smallvec",
622
1472
"tokio",
1473
+
"want",
1474
+
]
1475
+
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]]
1494
+
name = "hyper-timeout"
1495
+
version = "0.5.2"
1496
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1497
+
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
1498
+
dependencies = [
1499
+
"hyper",
1500
+
"hyper-util",
1501
+
"pin-project-lite",
1502
+
"tokio",
1503
+
"tower-service",
623
1504
]
624
1505
625
1506
[[package]]
···
628
1509
source = "registry+https://github.com/rust-lang/crates.io-index"
629
1510
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
630
1511
dependencies = [
1512
+
"base64",
631
1513
"bytes",
1514
+
"futures-channel",
632
1515
"futures-core",
1516
+
"futures-util",
633
1517
"http",
634
1518
"http-body",
635
1519
"hyper",
1520
+
"ipnet",
1521
+
"libc",
1522
+
"percent-encoding",
636
1523
"pin-project-lite",
1524
+
"socket2",
1525
+
"system-configuration",
637
1526
"tokio",
638
1527
"tower-service",
1528
+
"tracing",
1529
+
"windows-registry",
1530
+
]
1531
+
1532
+
[[package]]
1533
+
name = "iana-time-zone"
1534
+
version = "0.1.63"
1535
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1536
+
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
1537
+
dependencies = [
1538
+
"android_system_properties",
1539
+
"core-foundation-sys",
1540
+
"iana-time-zone-haiku",
1541
+
"js-sys",
1542
+
"log",
1543
+
"wasm-bindgen",
1544
+
"windows-core",
1545
+
]
1546
+
1547
+
[[package]]
1548
+
name = "iana-time-zone-haiku"
1549
+
version = "0.1.2"
1550
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1551
+
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
1552
+
dependencies = [
1553
+
"cc",
639
1554
]
640
1555
641
1556
[[package]]
···
725
1640
]
726
1641
727
1642
[[package]]
1643
+
name = "ident_case"
1644
+
version = "1.0.1"
1645
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1646
+
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
1647
+
1648
+
[[package]]
728
1649
name = "idna"
729
1650
version = "1.0.3"
730
1651
source = "registry+https://github.com/rust-lang/crates.io-index"
···
747
1668
748
1669
[[package]]
749
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"
750
1682
version = "2.10.0"
751
1683
source = "registry+https://github.com/rust-lang/crates.io-index"
752
1684
checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
753
1685
dependencies = [
754
1686
"equivalent",
755
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",
1698
+
]
1699
+
1700
+
[[package]]
1701
+
name = "inout"
1702
+
version = "0.1.4"
1703
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1704
+
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
1705
+
dependencies = [
1706
+
"generic-array",
1707
+
]
1708
+
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",
756
1716
]
757
1717
758
1718
[[package]]
···
767
1727
]
768
1728
769
1729
[[package]]
1730
+
name = "ipld-core"
1731
+
version = "0.4.2"
1732
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1733
+
checksum = "104718b1cc124d92a6d01ca9c9258a7df311405debb3408c445a36452f9bf8db"
1734
+
dependencies = [
1735
+
"cid",
1736
+
"serde",
1737
+
"serde_bytes",
1738
+
]
1739
+
1740
+
[[package]]
1741
+
name = "ipnet"
1742
+
version = "2.11.0"
1743
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1744
+
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
1745
+
1746
+
[[package]]
1747
+
name = "iri-string"
1748
+
version = "0.7.9"
1749
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1750
+
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
1751
+
dependencies = [
1752
+
"memchr",
1753
+
"serde",
1754
+
]
1755
+
1756
+
[[package]]
1757
+
name = "itertools"
1758
+
version = "0.12.1"
1759
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1760
+
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
1761
+
dependencies = [
1762
+
"either",
1763
+
]
1764
+
1765
+
[[package]]
770
1766
name = "itoa"
771
1767
version = "1.0.15"
772
1768
source = "registry+https://github.com/rust-lang/crates.io-index"
773
1769
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
774
1770
775
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]]
1895
+
name = "jobserver"
1896
+
version = "0.1.33"
1897
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1898
+
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
1899
+
dependencies = [
1900
+
"getrandom 0.3.4",
1901
+
"libc",
1902
+
]
1903
+
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]]
1922
+
name = "js-sys"
1923
+
version = "0.3.77"
1924
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1925
+
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
1926
+
dependencies = [
1927
+
"once_cell",
1928
+
"wasm-bindgen",
1929
+
]
1930
+
1931
+
[[package]]
1932
+
name = "jwt-compact"
1933
+
version = "0.8.0"
1934
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1935
+
checksum = "25cb2458ca54de48ef237ac0d68d4e80e512ae81d6aeb9775f4c835da0d193d3"
1936
+
dependencies = [
1937
+
"anyhow",
1938
+
"base64ct",
1939
+
"chrono",
1940
+
"ciborium",
1941
+
"hmac",
1942
+
"lazy_static",
1943
+
"rand_core 0.6.4",
1944
+
"secp256k1",
1945
+
"serde",
1946
+
"serde_json",
1947
+
"sha2",
1948
+
"smallvec",
1949
+
"subtle",
1950
+
"zeroize",
1951
+
]
1952
+
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]]
776
1977
name = "lazy_static"
777
1978
version = "1.5.0"
778
1979
source = "registry+https://github.com/rust-lang/crates.io-index"
···
782
1983
]
783
1984
784
1985
[[package]]
1986
+
name = "lazycell"
1987
+
version = "1.3.0"
1988
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1989
+
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
1990
+
1991
+
[[package]]
1992
+
name = "lettre"
1993
+
version = "0.11.18"
1994
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1995
+
checksum = "5cb54db6ff7a89efac87dba5baeac57bb9ccd726b49a9b6f21fb92b3966aaf56"
1996
+
dependencies = [
1997
+
"async-trait",
1998
+
"base64",
1999
+
"chumsky",
2000
+
"email-encoding",
2001
+
"email_address",
2002
+
"fastrand",
2003
+
"futures-io",
2004
+
"futures-util",
2005
+
"httpdate",
2006
+
"idna",
2007
+
"mime",
2008
+
"nom 8.0.0",
2009
+
"percent-encoding",
2010
+
"quoted_printable",
2011
+
"rustls",
2012
+
"socket2",
2013
+
"tokio",
2014
+
"tokio-rustls",
2015
+
"url",
2016
+
"webpki-roots 1.0.2",
2017
+
]
2018
+
2019
+
[[package]]
785
2020
name = "libc"
786
2021
version = "0.2.175"
787
2022
source = "registry+https://github.com/rust-lang/crates.io-index"
788
2023
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
789
2024
790
2025
[[package]]
2026
+
name = "libloading"
2027
+
version = "0.8.8"
2028
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2029
+
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
2030
+
dependencies = [
2031
+
"cfg-if",
2032
+
"windows-targets 0.52.6",
2033
+
]
2034
+
2035
+
[[package]]
791
2036
name = "libm"
792
2037
version = "0.2.15"
793
2038
source = "registry+https://github.com/rust-lang/crates.io-index"
···
806
2051
807
2052
[[package]]
808
2053
name = "libsqlite3-sys"
809
-
version = "0.27.0"
2054
+
version = "0.30.1"
810
2055
source = "registry+https://github.com/rust-lang/crates.io-index"
811
-
checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716"
2056
+
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
812
2057
dependencies = [
813
2058
"cc",
814
2059
"pkg-config",
···
817
2062
818
2063
[[package]]
819
2064
name = "linux-raw-sys"
820
-
version = "0.9.4"
2065
+
version = "0.4.15"
821
2066
source = "registry+https://github.com/rust-lang/crates.io-index"
822
-
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
2067
+
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
823
2068
824
2069
[[package]]
825
2070
name = "litemap"
···
844
2089
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
845
2090
846
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]]
847
2109
name = "matchers"
848
2110
version = "0.1.0"
849
2111
source = "registry+https://github.com/rust-lang/crates.io-index"
···
854
2116
855
2117
[[package]]
856
2118
name = "matchit"
857
-
version = "0.7.3"
2119
+
version = "0.8.4"
858
2120
source = "registry+https://github.com/rust-lang/crates.io-index"
859
-
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
2121
+
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
860
2122
861
2123
[[package]]
862
2124
name = "md-5"
···
875
2137
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
876
2138
877
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]]
878
2162
name = "mime"
879
2163
version = "0.3.17"
880
2164
source = "registry+https://github.com/rust-lang/crates.io-index"
···
893
2177
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
894
2178
dependencies = [
895
2179
"adler2",
2180
+
"simd-adler32",
896
2181
]
897
2182
898
2183
[[package]]
···
902
2187
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
903
2188
dependencies = [
904
2189
"libc",
905
-
"wasi 0.11.1+wasi-snapshot-preview1",
2190
+
"wasi",
906
2191
"windows-sys 0.59.0",
907
2192
]
908
2193
909
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]]
910
2218
name = "nom"
911
2219
version = "7.1.3"
912
2220
source = "registry+https://github.com/rust-lang/crates.io-index"
···
917
2225
]
918
2226
919
2227
[[package]]
2228
+
name = "nom"
2229
+
version = "8.0.0"
2230
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2231
+
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
2232
+
dependencies = [
2233
+
"memchr",
2234
+
]
2235
+
2236
+
[[package]]
2237
+
name = "nonempty"
2238
+
version = "0.7.0"
2239
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2240
+
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
2241
+
2242
+
[[package]]
2243
+
name = "nonzero_ext"
2244
+
version = "0.3.0"
2245
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2246
+
checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21"
2247
+
2248
+
[[package]]
920
2249
name = "nu-ansi-term"
921
2250
version = "0.46.0"
922
2251
source = "registry+https://github.com/rust-lang/crates.io-index"
···
938
2267
"num-integer",
939
2268
"num-iter",
940
2269
"num-traits",
941
-
"rand",
2270
+
"rand 0.8.5",
942
2271
"smallvec",
943
2272
"zeroize",
944
2273
]
945
2274
946
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]]
947
2282
name = "num-integer"
948
2283
version = "0.1.46"
949
2284
source = "registry+https://github.com/rust-lang/crates.io-index"
···
964
2299
]
965
2300
966
2301
[[package]]
2302
+
name = "num-modular"
2303
+
version = "0.6.1"
2304
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2305
+
checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f"
2306
+
2307
+
[[package]]
2308
+
name = "num-order"
2309
+
version = "1.2.0"
2310
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2311
+
checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6"
2312
+
dependencies = [
2313
+
"num-modular",
2314
+
]
2315
+
2316
+
[[package]]
967
2317
name = "num-traits"
968
2318
version = "0.2.19"
969
2319
source = "registry+https://github.com/rust-lang/crates.io-index"
···
989
2339
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
990
2340
991
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]]
992
2404
name = "overload"
993
2405
version = "0.1.1"
994
2406
source = "registry+https://github.com/rust-lang/crates.io-index"
995
2407
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
996
2408
997
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]]
2422
+
name = "parking"
2423
+
version = "2.2.1"
2424
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2425
+
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
2426
+
2427
+
[[package]]
998
2428
name = "parking_lot"
999
2429
version = "0.12.4"
1000
2430
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1018
2448
]
1019
2449
1020
2450
[[package]]
1021
-
name = "paste"
1022
-
version = "1.0.15"
2451
+
name = "password-hash"
2452
+
version = "0.5.0"
1023
2453
source = "registry+https://github.com/rust-lang/crates.io-index"
1024
-
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
2454
+
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
2455
+
dependencies = [
2456
+
"base64ct",
2457
+
"rand_core 0.6.4",
2458
+
"subtle",
2459
+
]
1025
2460
1026
2461
[[package]]
1027
-
name = "pds_bells_and_whistles"
1028
-
version = "0.1.0"
2462
+
name = "pbkdf2"
2463
+
version = "0.12.2"
2464
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2465
+
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
2466
+
dependencies = [
2467
+
"digest",
2468
+
"hmac",
2469
+
]
2470
+
2471
+
[[package]]
2472
+
name = "pds_gatekeeper"
2473
+
version = "0.1.2"
1029
2474
dependencies = [
2475
+
"anyhow",
2476
+
"aws-lc-rs",
1030
2477
"axum",
2478
+
"axum-template",
2479
+
"chrono",
1031
2480
"dotenvy",
2481
+
"handlebars",
2482
+
"hex",
2483
+
"html-escape",
2484
+
"hyper-util",
2485
+
"jacquard-common",
2486
+
"jacquard-identity",
2487
+
"josekit",
2488
+
"jwt-compact",
2489
+
"lettre",
2490
+
"multibase",
2491
+
"rand 0.9.2",
2492
+
"reqwest",
2493
+
"rust-embed",
2494
+
"rustls",
2495
+
"scrypt",
1032
2496
"serde",
1033
2497
"serde_json",
2498
+
"sha2",
1034
2499
"sqlx",
1035
2500
"tokio",
2501
+
"tower-http",
2502
+
"tower_governor",
1036
2503
"tracing",
1037
2504
"tracing-subscriber",
2505
+
"urlencoding",
1038
2506
]
1039
2507
1040
2508
[[package]]
···
1053
2521
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1054
2522
1055
2523
[[package]]
2524
+
name = "pest"
2525
+
version = "2.8.1"
2526
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2527
+
checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
2528
+
dependencies = [
2529
+
"memchr",
2530
+
"thiserror 2.0.14",
2531
+
"ucd-trie",
2532
+
]
2533
+
2534
+
[[package]]
2535
+
name = "pest_derive"
2536
+
version = "2.8.1"
2537
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2538
+
checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
2539
+
dependencies = [
2540
+
"pest",
2541
+
"pest_generator",
2542
+
]
2543
+
2544
+
[[package]]
2545
+
name = "pest_generator"
2546
+
version = "2.8.1"
2547
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2548
+
checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
2549
+
dependencies = [
2550
+
"pest",
2551
+
"pest_meta",
2552
+
"proc-macro2",
2553
+
"quote",
2554
+
"syn 2.0.105",
2555
+
]
2556
+
2557
+
[[package]]
2558
+
name = "pest_meta"
2559
+
version = "2.8.1"
2560
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2561
+
checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
2562
+
dependencies = [
2563
+
"pest",
2564
+
"sha2",
2565
+
]
2566
+
2567
+
[[package]]
2568
+
name = "pin-project"
2569
+
version = "1.1.10"
2570
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2571
+
checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
2572
+
dependencies = [
2573
+
"pin-project-internal",
2574
+
]
2575
+
2576
+
[[package]]
2577
+
name = "pin-project-internal"
2578
+
version = "1.1.10"
2579
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2580
+
checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
2581
+
dependencies = [
2582
+
"proc-macro2",
2583
+
"quote",
2584
+
"syn 2.0.105",
2585
+
]
2586
+
2587
+
[[package]]
1056
2588
name = "pin-project-lite"
1057
2589
version = "0.2.16"
1058
2590
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1092
2624
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
1093
2625
1094
2626
[[package]]
2627
+
name = "portable-atomic"
2628
+
version = "1.11.1"
2629
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2630
+
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
2631
+
2632
+
[[package]]
1095
2633
name = "potential_utf"
1096
2634
version = "0.1.2"
1097
2635
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1101
2639
]
1102
2640
1103
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]]
1104
2648
name = "ppv-lite86"
1105
2649
version = "0.2.21"
1106
2650
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1110
2654
]
1111
2655
1112
2656
[[package]]
2657
+
name = "prettyplease"
2658
+
version = "0.2.35"
2659
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2660
+
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
2661
+
dependencies = [
2662
+
"proc-macro2",
2663
+
"syn 2.0.105",
2664
+
]
2665
+
2666
+
[[package]]
2667
+
name = "primeorder"
2668
+
version = "0.13.6"
2669
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2670
+
checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6"
2671
+
dependencies = [
2672
+
"elliptic-curve",
2673
+
]
2674
+
2675
+
[[package]]
2676
+
name = "proc-macro-error"
2677
+
version = "1.0.4"
2678
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2679
+
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
2680
+
dependencies = [
2681
+
"proc-macro-error-attr",
2682
+
"proc-macro2",
2683
+
"quote",
2684
+
"syn 1.0.109",
2685
+
"version_check",
2686
+
]
2687
+
2688
+
[[package]]
2689
+
name = "proc-macro-error-attr"
2690
+
version = "1.0.4"
2691
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2692
+
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
2693
+
dependencies = [
2694
+
"proc-macro2",
2695
+
"quote",
2696
+
"version_check",
2697
+
]
2698
+
2699
+
[[package]]
1113
2700
name = "proc-macro2"
1114
2701
version = "1.0.97"
1115
2702
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1119
2706
]
1120
2707
1121
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]]
2722
+
name = "psm"
2723
+
version = "0.1.26"
2724
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2725
+
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
2726
+
dependencies = [
2727
+
"cc",
2728
+
]
2729
+
2730
+
[[package]]
2731
+
name = "quanta"
2732
+
version = "0.12.6"
2733
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2734
+
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
2735
+
dependencies = [
2736
+
"crossbeam-utils",
2737
+
"libc",
2738
+
"once_cell",
2739
+
"raw-cpuid",
2740
+
"wasi",
2741
+
"web-sys",
2742
+
"winapi",
2743
+
]
2744
+
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]]
1122
2801
name = "quote"
1123
2802
version = "1.0.40"
1124
2803
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1128
2807
]
1129
2808
1130
2809
[[package]]
2810
+
name = "quoted_printable"
2811
+
version = "0.5.1"
2812
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2813
+
checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73"
2814
+
2815
+
[[package]]
1131
2816
name = "r-efi"
1132
2817
version = "5.3.0"
1133
2818
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1140
2825
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1141
2826
dependencies = [
1142
2827
"libc",
1143
-
"rand_chacha",
1144
-
"rand_core",
2828
+
"rand_chacha 0.3.1",
2829
+
"rand_core 0.6.4",
2830
+
]
2831
+
2832
+
[[package]]
2833
+
name = "rand"
2834
+
version = "0.9.2"
2835
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2836
+
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
2837
+
dependencies = [
2838
+
"rand_chacha 0.9.0",
2839
+
"rand_core 0.9.3",
1145
2840
]
1146
2841
1147
2842
[[package]]
···
1151
2846
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1152
2847
dependencies = [
1153
2848
"ppv-lite86",
1154
-
"rand_core",
2849
+
"rand_core 0.6.4",
2850
+
]
2851
+
2852
+
[[package]]
2853
+
name = "rand_chacha"
2854
+
version = "0.9.0"
2855
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2856
+
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
2857
+
dependencies = [
2858
+
"ppv-lite86",
2859
+
"rand_core 0.9.3",
1155
2860
]
1156
2861
1157
2862
[[package]]
···
1164
2869
]
1165
2870
1166
2871
[[package]]
2872
+
name = "rand_core"
2873
+
version = "0.9.3"
2874
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2875
+
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
2876
+
dependencies = [
2877
+
"getrandom 0.3.4",
2878
+
]
2879
+
2880
+
[[package]]
2881
+
name = "range-traits"
2882
+
version = "0.3.2"
2883
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2884
+
checksum = "d20581732dd76fa913c7dff1a2412b714afe3573e94d41c34719de73337cc8ab"
2885
+
2886
+
[[package]]
2887
+
name = "raw-cpuid"
2888
+
version = "11.5.0"
2889
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2890
+
checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146"
2891
+
dependencies = [
2892
+
"bitflags",
2893
+
]
2894
+
2895
+
[[package]]
1167
2896
name = "redox_syscall"
1168
2897
version = "0.5.17"
1169
2898
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1173
2902
]
1174
2903
1175
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]]
1176
2925
name = "regex"
1177
-
version = "1.11.1"
2926
+
version = "1.12.2"
1178
2927
source = "registry+https://github.com/rust-lang/crates.io-index"
1179
-
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
2928
+
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
1180
2929
dependencies = [
1181
2930
"aho-corasick",
1182
2931
"memchr",
1183
-
"regex-automata 0.4.9",
2932
+
"regex-automata 0.4.13",
1184
2933
"regex-syntax 0.8.5",
1185
2934
]
1186
2935
···
1195
2944
1196
2945
[[package]]
1197
2946
name = "regex-automata"
1198
-
version = "0.4.9"
2947
+
version = "0.4.13"
1199
2948
source = "registry+https://github.com/rust-lang/crates.io-index"
1200
-
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
2949
+
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
1201
2950
dependencies = [
1202
2951
"aho-corasick",
1203
2952
"memchr",
···
1205
2954
]
1206
2955
1207
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]]
1208
2963
name = "regex-syntax"
1209
2964
version = "0.6.29"
1210
2965
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1217
2972
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
1218
2973
1219
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]]
1220
3030
name = "ring"
1221
3031
version = "0.17.14"
1222
3032
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1226
3036
"cfg-if",
1227
3037
"getrandom 0.2.16",
1228
3038
"libc",
1229
-
"untrusted",
3039
+
"untrusted 0.9.0",
1230
3040
"windows-sys 0.52.0",
1231
3041
]
1232
3042
···
1243
3053
"num-traits",
1244
3054
"pkcs1",
1245
3055
"pkcs8",
1246
-
"rand_core",
3056
+
"rand_core 0.6.4",
1247
3057
"signature",
1248
3058
"spki",
1249
3059
"subtle",
···
1251
3061
]
1252
3062
1253
3063
[[package]]
3064
+
name = "rust-embed"
3065
+
version = "8.7.2"
3066
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3067
+
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
3068
+
dependencies = [
3069
+
"rust-embed-impl",
3070
+
"rust-embed-utils",
3071
+
"walkdir",
3072
+
]
3073
+
3074
+
[[package]]
3075
+
name = "rust-embed-impl"
3076
+
version = "8.7.2"
3077
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3078
+
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
3079
+
dependencies = [
3080
+
"proc-macro2",
3081
+
"quote",
3082
+
"rust-embed-utils",
3083
+
"syn 2.0.105",
3084
+
"walkdir",
3085
+
]
3086
+
3087
+
[[package]]
3088
+
name = "rust-embed-utils"
3089
+
version = "8.7.2"
3090
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3091
+
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
3092
+
dependencies = [
3093
+
"globset",
3094
+
"sha2",
3095
+
"walkdir",
3096
+
]
3097
+
3098
+
[[package]]
1254
3099
name = "rustc-demangle"
1255
3100
version = "0.1.26"
1256
3101
source = "registry+https://github.com/rust-lang/crates.io-index"
1257
3102
checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
1258
3103
1259
3104
[[package]]
3105
+
name = "rustc-hash"
3106
+
version = "1.1.0"
3107
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3108
+
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
3109
+
3110
+
[[package]]
3111
+
name = "rustc-hash"
3112
+
version = "2.1.1"
3113
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3114
+
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
3115
+
3116
+
[[package]]
1260
3117
name = "rustix"
1261
-
version = "1.0.8"
3118
+
version = "0.38.44"
1262
3119
source = "registry+https://github.com/rust-lang/crates.io-index"
1263
-
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
3120
+
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
1264
3121
dependencies = [
1265
3122
"bitflags",
1266
3123
"errno",
1267
3124
"libc",
1268
3125
"linux-raw-sys",
1269
-
"windows-sys 0.60.2",
3126
+
"windows-sys 0.59.0",
1270
3127
]
1271
3128
1272
3129
[[package]]
1273
3130
name = "rustls"
1274
-
version = "0.21.12"
3131
+
version = "0.23.31"
1275
3132
source = "registry+https://github.com/rust-lang/crates.io-index"
1276
-
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
3133
+
checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
1277
3134
dependencies = [
3135
+
"aws-lc-rs",
3136
+
"log",
3137
+
"once_cell",
1278
3138
"ring",
3139
+
"rustls-pki-types",
1279
3140
"rustls-webpki",
1280
-
"sct",
3141
+
"subtle",
3142
+
"zeroize",
1281
3143
]
1282
3144
1283
3145
[[package]]
1284
-
name = "rustls-pemfile"
1285
-
version = "1.0.4"
3146
+
name = "rustls-pki-types"
3147
+
version = "1.12.0"
1286
3148
source = "registry+https://github.com/rust-lang/crates.io-index"
1287
-
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
3149
+
checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
1288
3150
dependencies = [
1289
-
"base64",
3151
+
"web-time",
3152
+
"zeroize",
1290
3153
]
1291
3154
1292
3155
[[package]]
1293
3156
name = "rustls-webpki"
1294
-
version = "0.101.7"
3157
+
version = "0.103.4"
1295
3158
source = "registry+https://github.com/rust-lang/crates.io-index"
1296
-
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
3159
+
checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
1297
3160
dependencies = [
3161
+
"aws-lc-rs",
1298
3162
"ring",
1299
-
"untrusted",
3163
+
"rustls-pki-types",
3164
+
"untrusted 0.9.0",
1300
3165
]
1301
3166
1302
3167
[[package]]
···
1312
3177
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
1313
3178
1314
3179
[[package]]
3180
+
name = "salsa20"
3181
+
version = "0.10.2"
3182
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3183
+
checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
3184
+
dependencies = [
3185
+
"cipher",
3186
+
]
3187
+
3188
+
[[package]]
3189
+
name = "same-file"
3190
+
version = "1.0.6"
3191
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3192
+
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
3193
+
dependencies = [
3194
+
"winapi-util",
3195
+
]
3196
+
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]]
1315
3222
name = "scopeguard"
1316
3223
version = "1.2.0"
1317
3224
source = "registry+https://github.com/rust-lang/crates.io-index"
1318
3225
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1319
3226
1320
3227
[[package]]
1321
-
name = "sct"
1322
-
version = "0.7.1"
3228
+
name = "scrypt"
3229
+
version = "0.11.0"
3230
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3231
+
checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
3232
+
dependencies = [
3233
+
"password-hash",
3234
+
"pbkdf2",
3235
+
"salsa20",
3236
+
"sha2",
3237
+
]
3238
+
3239
+
[[package]]
3240
+
name = "sec1"
3241
+
version = "0.7.3"
3242
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3243
+
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
3244
+
dependencies = [
3245
+
"base16ct",
3246
+
"der",
3247
+
"generic-array",
3248
+
"pkcs8",
3249
+
"subtle",
3250
+
"zeroize",
3251
+
]
3252
+
3253
+
[[package]]
3254
+
name = "secp256k1"
3255
+
version = "0.28.2"
3256
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3257
+
checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10"
3258
+
dependencies = [
3259
+
"secp256k1-sys",
3260
+
]
3261
+
3262
+
[[package]]
3263
+
name = "secp256k1-sys"
3264
+
version = "0.9.2"
1323
3265
source = "registry+https://github.com/rust-lang/crates.io-index"
1324
-
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
3266
+
checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb"
1325
3267
dependencies = [
1326
-
"ring",
1327
-
"untrusted",
3268
+
"cc",
1328
3269
]
1329
3270
1330
3271
[[package]]
1331
3272
name = "serde"
1332
-
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"
1333
3284
source = "registry+https://github.com/rust-lang/crates.io-index"
1334
-
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
3285
+
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
3286
+
dependencies = [
3287
+
"serde",
3288
+
"serde_core",
3289
+
]
3290
+
3291
+
[[package]]
3292
+
name = "serde_core"
3293
+
version = "1.0.228"
3294
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3295
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1335
3296
dependencies = [
1336
3297
"serde_derive",
1337
3298
]
1338
3299
1339
3300
[[package]]
1340
3301
name = "serde_derive"
1341
-
version = "1.0.219"
3302
+
version = "1.0.228"
1342
3303
source = "registry+https://github.com/rust-lang/crates.io-index"
1343
-
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
3304
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1344
3305
dependencies = [
1345
3306
"proc-macro2",
1346
3307
"quote",
···
1348
3309
]
1349
3310
1350
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",
3334
+
]
3335
+
3336
+
[[package]]
1351
3337
name = "serde_json"
1352
-
version = "1.0.142"
3338
+
version = "1.0.145"
1353
3339
source = "registry+https://github.com/rust-lang/crates.io-index"
1354
-
checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
3340
+
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
1355
3341
dependencies = [
3342
+
"indexmap 2.10.0",
1356
3343
"itoa",
1357
3344
"memchr",
1358
3345
"ryu",
1359
3346
"serde",
3347
+
"serde_core",
1360
3348
]
1361
3349
1362
3350
[[package]]
···
1370
3358
]
1371
3359
1372
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]]
1373
3372
name = "serde_urlencoded"
1374
3373
version = "0.7.1"
1375
3374
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1382
3381
]
1383
3382
1384
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]]
1385
3415
name = "sha1"
1386
3416
version = "0.10.6"
1387
3417
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1434
3464
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
1435
3465
dependencies = [
1436
3466
"digest",
1437
-
"rand_core",
3467
+
"rand_core 0.6.4",
1438
3468
]
3469
+
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"
1439
3475
1440
3476
[[package]]
1441
3477
name = "slab"
···
1448
3484
version = "1.15.1"
1449
3485
source = "registry+https://github.com/rust-lang/crates.io-index"
1450
3486
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
3487
+
dependencies = [
3488
+
"serde",
3489
+
]
3490
+
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
+
]
1451
3500
1452
3501
[[package]]
1453
3502
name = "socket2"
···
1469
3518
]
1470
3519
1471
3520
[[package]]
1472
-
name = "spki"
1473
-
version = "0.7.3"
3521
+
name = "spinning_top"
3522
+
version = "0.3.0"
1474
3523
source = "registry+https://github.com/rust-lang/crates.io-index"
1475
-
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
3524
+
checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300"
1476
3525
dependencies = [
1477
-
"base64ct",
1478
-
"der",
3526
+
"lock_api",
1479
3527
]
1480
3528
1481
3529
[[package]]
1482
-
name = "sqlformat"
1483
-
version = "0.2.6"
3530
+
name = "spki"
3531
+
version = "0.7.3"
1484
3532
source = "registry+https://github.com/rust-lang/crates.io-index"
1485
-
checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790"
3533
+
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
1486
3534
dependencies = [
1487
-
"nom",
1488
-
"unicode_categories",
3535
+
"base64ct",
3536
+
"der",
1489
3537
]
1490
3538
1491
3539
[[package]]
1492
3540
name = "sqlx"
1493
-
version = "0.7.4"
3541
+
version = "0.8.6"
1494
3542
source = "registry+https://github.com/rust-lang/crates.io-index"
1495
-
checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa"
3543
+
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
1496
3544
dependencies = [
1497
3545
"sqlx-core",
1498
3546
"sqlx-macros",
···
1503
3551
1504
3552
[[package]]
1505
3553
name = "sqlx-core"
1506
-
version = "0.7.4"
3554
+
version = "0.8.6"
1507
3555
source = "registry+https://github.com/rust-lang/crates.io-index"
1508
-
checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6"
3556
+
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
1509
3557
dependencies = [
1510
-
"ahash",
1511
-
"atoi",
1512
-
"byteorder",
3558
+
"base64",
1513
3559
"bytes",
3560
+
"chrono",
1514
3561
"crc",
1515
3562
"crossbeam-queue",
1516
3563
"either",
1517
3564
"event-listener",
1518
-
"futures-channel",
1519
3565
"futures-core",
1520
3566
"futures-intrusive",
1521
3567
"futures-io",
1522
3568
"futures-util",
3569
+
"hashbrown 0.15.5",
1523
3570
"hashlink",
1524
-
"hex",
1525
-
"indexmap",
3571
+
"indexmap 2.10.0",
1526
3572
"log",
1527
3573
"memchr",
1528
3574
"once_cell",
1529
-
"paste",
1530
3575
"percent-encoding",
1531
3576
"rustls",
1532
-
"rustls-pemfile",
1533
3577
"serde",
1534
3578
"serde_json",
1535
3579
"sha2",
1536
3580
"smallvec",
1537
-
"sqlformat",
1538
-
"thiserror",
3581
+
"thiserror 2.0.14",
1539
3582
"tokio",
1540
3583
"tokio-stream",
1541
3584
"tracing",
1542
3585
"url",
1543
-
"webpki-roots",
3586
+
"webpki-roots 0.26.11",
1544
3587
]
1545
3588
1546
3589
[[package]]
1547
3590
name = "sqlx-macros"
1548
-
version = "0.7.4"
3591
+
version = "0.8.6"
1549
3592
source = "registry+https://github.com/rust-lang/crates.io-index"
1550
-
checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127"
3593
+
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
1551
3594
dependencies = [
1552
3595
"proc-macro2",
1553
3596
"quote",
1554
3597
"sqlx-core",
1555
3598
"sqlx-macros-core",
1556
-
"syn 1.0.109",
3599
+
"syn 2.0.105",
1557
3600
]
1558
3601
1559
3602
[[package]]
1560
3603
name = "sqlx-macros-core"
1561
-
version = "0.7.4"
3604
+
version = "0.8.6"
1562
3605
source = "registry+https://github.com/rust-lang/crates.io-index"
1563
-
checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8"
3606
+
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
1564
3607
dependencies = [
1565
3608
"dotenvy",
1566
3609
"either",
1567
-
"heck",
3610
+
"heck 0.5.0",
1568
3611
"hex",
1569
3612
"once_cell",
1570
3613
"proc-macro2",
···
1574
3617
"sha2",
1575
3618
"sqlx-core",
1576
3619
"sqlx-mysql",
3620
+
"sqlx-postgres",
1577
3621
"sqlx-sqlite",
1578
-
"syn 1.0.109",
1579
-
"tempfile",
3622
+
"syn 2.0.105",
1580
3623
"tokio",
1581
3624
"url",
1582
3625
]
1583
3626
1584
3627
[[package]]
1585
3628
name = "sqlx-mysql"
1586
-
version = "0.7.4"
3629
+
version = "0.8.6"
1587
3630
source = "registry+https://github.com/rust-lang/crates.io-index"
1588
-
checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418"
3631
+
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
1589
3632
dependencies = [
1590
3633
"atoi",
1591
3634
"base64",
1592
3635
"bitflags",
1593
3636
"byteorder",
1594
3637
"bytes",
3638
+
"chrono",
1595
3639
"crc",
1596
3640
"digest",
1597
3641
"dotenvy",
···
1610
3654
"memchr",
1611
3655
"once_cell",
1612
3656
"percent-encoding",
1613
-
"rand",
3657
+
"rand 0.8.5",
1614
3658
"rsa",
1615
3659
"serde",
1616
3660
"sha1",
···
1618
3662
"smallvec",
1619
3663
"sqlx-core",
1620
3664
"stringprep",
1621
-
"thiserror",
3665
+
"thiserror 2.0.14",
1622
3666
"tracing",
1623
3667
"whoami",
1624
3668
]
1625
3669
1626
3670
[[package]]
1627
3671
name = "sqlx-postgres"
1628
-
version = "0.7.4"
3672
+
version = "0.8.6"
1629
3673
source = "registry+https://github.com/rust-lang/crates.io-index"
1630
-
checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e"
3674
+
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
1631
3675
dependencies = [
1632
3676
"atoi",
1633
3677
"base64",
1634
3678
"bitflags",
1635
3679
"byteorder",
3680
+
"chrono",
1636
3681
"crc",
1637
3682
"dotenvy",
1638
3683
"etcetera",
1639
3684
"futures-channel",
1640
3685
"futures-core",
1641
-
"futures-io",
1642
3686
"futures-util",
1643
3687
"hex",
1644
3688
"hkdf",
···
1649
3693
"md-5",
1650
3694
"memchr",
1651
3695
"once_cell",
1652
-
"rand",
3696
+
"rand 0.8.5",
1653
3697
"serde",
1654
3698
"serde_json",
1655
3699
"sha2",
1656
3700
"smallvec",
1657
3701
"sqlx-core",
1658
3702
"stringprep",
1659
-
"thiserror",
3703
+
"thiserror 2.0.14",
1660
3704
"tracing",
1661
3705
"whoami",
1662
3706
]
1663
3707
1664
3708
[[package]]
1665
3709
name = "sqlx-sqlite"
1666
-
version = "0.7.4"
3710
+
version = "0.8.6"
1667
3711
source = "registry+https://github.com/rust-lang/crates.io-index"
1668
-
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
3712
+
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
1669
3713
dependencies = [
1670
3714
"atoi",
3715
+
"chrono",
1671
3716
"flume",
1672
3717
"futures-channel",
1673
3718
"futures-core",
···
1678
3723
"log",
1679
3724
"percent-encoding",
1680
3725
"serde",
3726
+
"serde_urlencoded",
1681
3727
"sqlx-core",
3728
+
"thiserror 2.0.14",
1682
3729
"tracing",
1683
3730
"url",
1684
-
"urlencoding",
1685
3731
]
1686
3732
1687
3733
[[package]]
···
1691
3737
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1692
3738
1693
3739
[[package]]
3740
+
name = "stacker"
3741
+
version = "0.1.21"
3742
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3743
+
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
3744
+
dependencies = [
3745
+
"cc",
3746
+
"cfg-if",
3747
+
"libc",
3748
+
"psm",
3749
+
"windows-sys 0.59.0",
3750
+
]
3751
+
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]]
1694
3779
name = "stringprep"
1695
3780
version = "0.1.5"
1696
3781
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1700
3785
"unicode-normalization",
1701
3786
"unicode-properties",
1702
3787
]
3788
+
3789
+
[[package]]
3790
+
name = "strsim"
3791
+
version = "0.11.1"
3792
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3793
+
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
1703
3794
1704
3795
[[package]]
1705
3796
name = "subtle"
···
1734
3825
version = "1.0.2"
1735
3826
source = "registry+https://github.com/rust-lang/crates.io-index"
1736
3827
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
3828
+
dependencies = [
3829
+
"futures-core",
3830
+
]
1737
3831
1738
3832
[[package]]
1739
3833
name = "synstructure"
···
1747
3841
]
1748
3842
1749
3843
[[package]]
1750
-
name = "tempfile"
1751
-
version = "3.20.0"
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"
1752
3857
source = "registry+https://github.com/rust-lang/crates.io-index"
1753
-
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
3858
+
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
1754
3859
dependencies = [
1755
-
"fastrand",
1756
-
"getrandom 0.3.3",
1757
-
"once_cell",
1758
-
"rustix",
1759
-
"windows-sys 0.59.0",
3860
+
"core-foundation-sys",
3861
+
"libc",
1760
3862
]
1761
3863
1762
3864
[[package]]
···
1765
3867
source = "registry+https://github.com/rust-lang/crates.io-index"
1766
3868
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
1767
3869
dependencies = [
1768
-
"thiserror-impl",
3870
+
"thiserror-impl 1.0.69",
3871
+
]
3872
+
3873
+
[[package]]
3874
+
name = "thiserror"
3875
+
version = "2.0.14"
3876
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3877
+
checksum = "0b0949c3a6c842cbde3f1686d6eea5a010516deb7085f79db747562d4102f41e"
3878
+
dependencies = [
3879
+
"thiserror-impl 2.0.14",
1769
3880
]
1770
3881
1771
3882
[[package]]
···
1780
3891
]
1781
3892
1782
3893
[[package]]
3894
+
name = "thiserror-impl"
3895
+
version = "2.0.14"
3896
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3897
+
checksum = "cc5b44b4ab9c2fdd0e0512e6bece8388e214c0749f5862b114cc5b7a25daf227"
3898
+
dependencies = [
3899
+
"proc-macro2",
3900
+
"quote",
3901
+
"syn 2.0.105",
3902
+
]
3903
+
3904
+
[[package]]
1783
3905
name = "thread_local"
1784
3906
version = "1.1.9"
1785
3907
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1789
3911
]
1790
3912
1791
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]]
1792
3945
name = "tinystr"
1793
3946
version = "0.8.1"
1794
3947
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1844
3997
]
1845
3998
1846
3999
[[package]]
4000
+
name = "tokio-rustls"
4001
+
version = "0.26.2"
4002
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4003
+
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
4004
+
dependencies = [
4005
+
"rustls",
4006
+
"tokio",
4007
+
]
4008
+
4009
+
[[package]]
1847
4010
name = "tokio-stream"
1848
4011
version = "0.1.17"
1849
4012
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1855
4018
]
1856
4019
1857
4020
[[package]]
4021
+
name = "tokio-util"
4022
+
version = "0.7.17"
4023
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4024
+
checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594"
4025
+
dependencies = [
4026
+
"bytes",
4027
+
"futures-core",
4028
+
"futures-sink",
4029
+
"pin-project-lite",
4030
+
"tokio",
4031
+
]
4032
+
4033
+
[[package]]
4034
+
name = "tonic"
4035
+
version = "0.14.1"
4036
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4037
+
checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40"
4038
+
dependencies = [
4039
+
"async-trait",
4040
+
"axum",
4041
+
"base64",
4042
+
"bytes",
4043
+
"h2",
4044
+
"http",
4045
+
"http-body",
4046
+
"http-body-util",
4047
+
"hyper",
4048
+
"hyper-timeout",
4049
+
"hyper-util",
4050
+
"percent-encoding",
4051
+
"pin-project",
4052
+
"socket2",
4053
+
"sync_wrapper",
4054
+
"tokio",
4055
+
"tokio-stream",
4056
+
"tower",
4057
+
"tower-layer",
4058
+
"tower-service",
4059
+
"tracing",
4060
+
]
4061
+
4062
+
[[package]]
1858
4063
name = "tower"
1859
4064
version = "0.5.2"
1860
4065
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1862
4067
dependencies = [
1863
4068
"futures-core",
1864
4069
"futures-util",
4070
+
"indexmap 2.10.0",
1865
4071
"pin-project-lite",
4072
+
"slab",
1866
4073
"sync_wrapper",
1867
4074
"tokio",
4075
+
"tokio-util",
1868
4076
"tower-layer",
1869
4077
"tower-service",
1870
4078
"tracing",
1871
4079
]
1872
4080
1873
4081
[[package]]
4082
+
name = "tower-http"
4083
+
version = "0.6.6"
4084
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4085
+
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
4086
+
dependencies = [
4087
+
"async-compression",
4088
+
"bitflags",
4089
+
"bytes",
4090
+
"futures-core",
4091
+
"futures-util",
4092
+
"http",
4093
+
"http-body",
4094
+
"iri-string",
4095
+
"pin-project-lite",
4096
+
"tokio",
4097
+
"tokio-util",
4098
+
"tower",
4099
+
"tower-layer",
4100
+
"tower-service",
4101
+
]
4102
+
4103
+
[[package]]
1874
4104
name = "tower-layer"
1875
4105
version = "0.3.3"
1876
4106
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1881
4111
version = "0.3.3"
1882
4112
source = "registry+https://github.com/rust-lang/crates.io-index"
1883
4113
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
4114
+
4115
+
[[package]]
4116
+
name = "tower_governor"
4117
+
version = "0.8.0"
4118
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4119
+
checksum = "44de9b94d849d3c46e06a883d72d408c2de6403367b39df2b1c9d9e7b6736fe6"
4120
+
dependencies = [
4121
+
"axum",
4122
+
"forwarded-header-value",
4123
+
"governor",
4124
+
"http",
4125
+
"pin-project",
4126
+
"thiserror 2.0.14",
4127
+
"tonic",
4128
+
"tower",
4129
+
"tracing",
4130
+
]
1884
4131
1885
4132
[[package]]
1886
4133
name = "tracing"
···
1945
4192
]
1946
4193
1947
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]]
4206
+
name = "try-lock"
4207
+
version = "0.2.5"
4208
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4209
+
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
4210
+
4211
+
[[package]]
1948
4212
name = "typenum"
1949
4213
version = "1.18.0"
1950
4214
source = "registry+https://github.com/rust-lang/crates.io-index"
1951
4215
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
4216
+
4217
+
[[package]]
4218
+
name = "ucd-trie"
4219
+
version = "0.1.7"
4220
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4221
+
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
1952
4222
1953
4223
[[package]]
1954
4224
name = "unicode-bidi"
···
1984
4254
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
1985
4255
1986
4256
[[package]]
1987
-
name = "unicode_categories"
1988
-
version = "0.1.1"
4257
+
name = "unicode-width"
4258
+
version = "0.1.14"
4259
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4260
+
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
4261
+
4262
+
[[package]]
4263
+
name = "unsigned-varint"
4264
+
version = "0.8.0"
4265
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4266
+
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
4267
+
4268
+
[[package]]
4269
+
name = "untrusted"
4270
+
version = "0.7.1"
1989
4271
source = "registry+https://github.com/rust-lang/crates.io-index"
1990
-
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
4272
+
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
1991
4273
1992
4274
[[package]]
1993
4275
name = "untrusted"
···
2004
4286
"form_urlencoded",
2005
4287
"idna",
2006
4288
"percent-encoding",
4289
+
"serde",
2007
4290
]
2008
4291
2009
4292
[[package]]
···
2013
4296
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
2014
4297
2015
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]]
2016
4305
name = "utf8_iter"
2017
4306
version = "1.0.4"
2018
4307
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2037
4326
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
2038
4327
2039
4328
[[package]]
4329
+
name = "walkdir"
4330
+
version = "2.5.0"
4331
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4332
+
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
4333
+
dependencies = [
4334
+
"same-file",
4335
+
"winapi-util",
4336
+
]
4337
+
4338
+
[[package]]
4339
+
name = "want"
4340
+
version = "0.3.1"
4341
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4342
+
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
4343
+
dependencies = [
4344
+
"try-lock",
4345
+
]
4346
+
4347
+
[[package]]
2040
4348
name = "wasi"
2041
4349
version = "0.11.1+wasi-snapshot-preview1"
2042
4350
source = "registry+https://github.com/rust-lang/crates.io-index"
2043
4351
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
2044
4352
2045
4353
[[package]]
2046
-
name = "wasi"
2047
-
version = "0.14.2+wasi-0.2.4"
4354
+
name = "wasip2"
4355
+
version = "1.0.1+wasi-0.2.4"
2048
4356
source = "registry+https://github.com/rust-lang/crates.io-index"
2049
-
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
4357
+
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
2050
4358
dependencies = [
2051
-
"wit-bindgen-rt",
4359
+
"wit-bindgen",
2052
4360
]
2053
4361
2054
4362
[[package]]
···
2058
4366
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
2059
4367
2060
4368
[[package]]
4369
+
name = "wasm-bindgen"
4370
+
version = "0.2.100"
4371
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4372
+
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
4373
+
dependencies = [
4374
+
"cfg-if",
4375
+
"once_cell",
4376
+
"rustversion",
4377
+
"wasm-bindgen-macro",
4378
+
]
4379
+
4380
+
[[package]]
4381
+
name = "wasm-bindgen-backend"
4382
+
version = "0.2.100"
4383
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4384
+
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
4385
+
dependencies = [
4386
+
"bumpalo",
4387
+
"log",
4388
+
"proc-macro2",
4389
+
"quote",
4390
+
"syn 2.0.105",
4391
+
"wasm-bindgen-shared",
4392
+
]
4393
+
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]]
4408
+
name = "wasm-bindgen-macro"
4409
+
version = "0.2.100"
4410
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4411
+
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
4412
+
dependencies = [
4413
+
"quote",
4414
+
"wasm-bindgen-macro-support",
4415
+
]
4416
+
4417
+
[[package]]
4418
+
name = "wasm-bindgen-macro-support"
4419
+
version = "0.2.100"
4420
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4421
+
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
4422
+
dependencies = [
4423
+
"proc-macro2",
4424
+
"quote",
4425
+
"syn 2.0.105",
4426
+
"wasm-bindgen-backend",
4427
+
"wasm-bindgen-shared",
4428
+
]
4429
+
4430
+
[[package]]
4431
+
name = "wasm-bindgen-shared"
4432
+
version = "0.2.100"
4433
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4434
+
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
4435
+
dependencies = [
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",
4450
+
]
4451
+
4452
+
[[package]]
4453
+
name = "web-sys"
4454
+
version = "0.3.77"
4455
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4456
+
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
4457
+
dependencies = [
4458
+
"js-sys",
4459
+
"wasm-bindgen",
4460
+
]
4461
+
4462
+
[[package]]
4463
+
name = "web-time"
4464
+
version = "1.1.0"
4465
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4466
+
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
4467
+
dependencies = [
4468
+
"js-sys",
4469
+
"wasm-bindgen",
4470
+
]
4471
+
4472
+
[[package]]
2061
4473
name = "webpki-roots"
2062
-
version = "0.25.4"
4474
+
version = "0.26.11"
4475
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4476
+
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
4477
+
dependencies = [
4478
+
"webpki-roots 1.0.2",
4479
+
]
4480
+
4481
+
[[package]]
4482
+
name = "webpki-roots"
4483
+
version = "1.0.2"
4484
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4485
+
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
4486
+
dependencies = [
4487
+
"rustls-pki-types",
4488
+
]
4489
+
4490
+
[[package]]
4491
+
name = "which"
4492
+
version = "4.4.2"
2063
4493
source = "registry+https://github.com/rust-lang/crates.io-index"
2064
-
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
4494
+
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
4495
+
dependencies = [
4496
+
"either",
4497
+
"home",
4498
+
"once_cell",
4499
+
"rustix",
4500
+
]
2065
4501
2066
4502
[[package]]
2067
4503
name = "whoami"
···
2090
4526
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2091
4527
2092
4528
[[package]]
4529
+
name = "winapi-util"
4530
+
version = "0.1.9"
4531
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4532
+
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
4533
+
dependencies = [
4534
+
"windows-sys 0.59.0",
4535
+
]
4536
+
4537
+
[[package]]
2093
4538
name = "winapi-x86_64-pc-windows-gnu"
2094
4539
version = "0.4.0"
2095
4540
source = "registry+https://github.com/rust-lang/crates.io-index"
2096
4541
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2097
4542
2098
4543
[[package]]
4544
+
name = "windows-core"
4545
+
version = "0.61.2"
4546
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4547
+
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
4548
+
dependencies = [
4549
+
"windows-implement",
4550
+
"windows-interface",
4551
+
"windows-link 0.1.3",
4552
+
"windows-result",
4553
+
"windows-strings",
4554
+
]
4555
+
4556
+
[[package]]
4557
+
name = "windows-implement"
4558
+
version = "0.60.0"
4559
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4560
+
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
4561
+
dependencies = [
4562
+
"proc-macro2",
4563
+
"quote",
4564
+
"syn 2.0.105",
4565
+
]
4566
+
4567
+
[[package]]
4568
+
name = "windows-interface"
4569
+
version = "0.59.1"
4570
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4571
+
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
4572
+
dependencies = [
4573
+
"proc-macro2",
4574
+
"quote",
4575
+
"syn 2.0.105",
4576
+
]
4577
+
4578
+
[[package]]
2099
4579
name = "windows-link"
2100
4580
version = "0.1.3"
2101
4581
source = "registry+https://github.com/rust-lang/crates.io-index"
2102
4582
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
2103
4583
2104
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]]
4602
+
name = "windows-result"
4603
+
version = "0.3.4"
4604
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4605
+
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
4606
+
dependencies = [
4607
+
"windows-link 0.1.3",
4608
+
]
4609
+
4610
+
[[package]]
4611
+
name = "windows-strings"
4612
+
version = "0.4.2"
4613
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4614
+
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
4615
+
dependencies = [
4616
+
"windows-link 0.1.3",
4617
+
]
4618
+
4619
+
[[package]]
2105
4620
name = "windows-sys"
2106
4621
version = "0.48.0"
2107
4622
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2126
4641
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
2127
4642
dependencies = [
2128
4643
"windows-targets 0.52.6",
2129
-
]
2130
-
2131
-
[[package]]
2132
-
name = "windows-sys"
2133
-
version = "0.60.2"
2134
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2135
-
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
2136
-
dependencies = [
2137
-
"windows-targets 0.53.3",
2138
4644
]
2139
4645
2140
4646
[[package]]
···
2161
4667
"windows_aarch64_gnullvm 0.52.6",
2162
4668
"windows_aarch64_msvc 0.52.6",
2163
4669
"windows_i686_gnu 0.52.6",
2164
-
"windows_i686_gnullvm 0.52.6",
4670
+
"windows_i686_gnullvm",
2165
4671
"windows_i686_msvc 0.52.6",
2166
4672
"windows_x86_64_gnu 0.52.6",
2167
4673
"windows_x86_64_gnullvm 0.52.6",
···
2169
4675
]
2170
4676
2171
4677
[[package]]
2172
-
name = "windows-targets"
2173
-
version = "0.53.3"
2174
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2175
-
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
2176
-
dependencies = [
2177
-
"windows-link",
2178
-
"windows_aarch64_gnullvm 0.53.0",
2179
-
"windows_aarch64_msvc 0.53.0",
2180
-
"windows_i686_gnu 0.53.0",
2181
-
"windows_i686_gnullvm 0.53.0",
2182
-
"windows_i686_msvc 0.53.0",
2183
-
"windows_x86_64_gnu 0.53.0",
2184
-
"windows_x86_64_gnullvm 0.53.0",
2185
-
"windows_x86_64_msvc 0.53.0",
2186
-
]
2187
-
2188
-
[[package]]
2189
4678
name = "windows_aarch64_gnullvm"
2190
4679
version = "0.48.5"
2191
4680
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2198
4687
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2199
4688
2200
4689
[[package]]
2201
-
name = "windows_aarch64_gnullvm"
2202
-
version = "0.53.0"
2203
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2204
-
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
2205
-
2206
-
[[package]]
2207
4690
name = "windows_aarch64_msvc"
2208
4691
version = "0.48.5"
2209
4692
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2216
4699
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2217
4700
2218
4701
[[package]]
2219
-
name = "windows_aarch64_msvc"
2220
-
version = "0.53.0"
2221
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2222
-
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
2223
-
2224
-
[[package]]
2225
4702
name = "windows_i686_gnu"
2226
4703
version = "0.48.5"
2227
4704
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2234
4711
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2235
4712
2236
4713
[[package]]
2237
-
name = "windows_i686_gnu"
2238
-
version = "0.53.0"
2239
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2240
-
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
2241
-
2242
-
[[package]]
2243
4714
name = "windows_i686_gnullvm"
2244
4715
version = "0.52.6"
2245
4716
source = "registry+https://github.com/rust-lang/crates.io-index"
2246
4717
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2247
4718
2248
4719
[[package]]
2249
-
name = "windows_i686_gnullvm"
2250
-
version = "0.53.0"
2251
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2252
-
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
2253
-
2254
-
[[package]]
2255
4720
name = "windows_i686_msvc"
2256
4721
version = "0.48.5"
2257
4722
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2264
4729
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2265
4730
2266
4731
[[package]]
2267
-
name = "windows_i686_msvc"
2268
-
version = "0.53.0"
2269
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2270
-
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
2271
-
2272
-
[[package]]
2273
4732
name = "windows_x86_64_gnu"
2274
4733
version = "0.48.5"
2275
4734
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2282
4741
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2283
4742
2284
4743
[[package]]
2285
-
name = "windows_x86_64_gnu"
2286
-
version = "0.53.0"
2287
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2288
-
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
2289
-
2290
-
[[package]]
2291
4744
name = "windows_x86_64_gnullvm"
2292
4745
version = "0.48.5"
2293
4746
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2300
4753
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2301
4754
2302
4755
[[package]]
2303
-
name = "windows_x86_64_gnullvm"
2304
-
version = "0.53.0"
2305
-
source = "registry+https://github.com/rust-lang/crates.io-index"
2306
-
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
2307
-
2308
-
[[package]]
2309
4756
name = "windows_x86_64_msvc"
2310
4757
version = "0.48.5"
2311
4758
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2318
4765
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2319
4766
2320
4767
[[package]]
2321
-
name = "windows_x86_64_msvc"
2322
-
version = "0.53.0"
4768
+
name = "wit-bindgen"
4769
+
version = "0.46.0"
2323
4770
source = "registry+https://github.com/rust-lang/crates.io-index"
2324
-
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
4771
+
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
2325
4772
2326
4773
[[package]]
2327
-
name = "wit-bindgen-rt"
2328
-
version = "0.39.0"
4774
+
name = "writeable"
4775
+
version = "0.6.1"
2329
4776
source = "registry+https://github.com/rust-lang/crates.io-index"
2330
-
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
2331
-
dependencies = [
2332
-
"bitflags",
2333
-
]
4777
+
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
2334
4778
2335
4779
[[package]]
2336
-
name = "writeable"
2337
-
version = "0.6.1"
4780
+
name = "yansi"
4781
+
version = "1.0.1"
2338
4782
source = "registry+https://github.com/rust-lang/crates.io-index"
2339
-
checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
4783
+
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
2340
4784
2341
4785
[[package]]
2342
4786
name = "yoke"
···
2408
4852
version = "1.8.1"
2409
4853
source = "registry+https://github.com/rust-lang/crates.io-index"
2410
4854
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
4855
+
dependencies = [
4856
+
"zeroize_derive",
4857
+
]
4858
+
4859
+
[[package]]
4860
+
name = "zeroize_derive"
4861
+
version = "1.4.2"
4862
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4863
+
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
4864
+
dependencies = [
4865
+
"proc-macro2",
4866
+
"quote",
4867
+
"syn 2.0.105",
4868
+
]
2411
4869
2412
4870
[[package]]
2413
4871
name = "zerotrie"
···
2441
4899
"quote",
2442
4900
"syn 2.0.105",
2443
4901
]
4902
+
4903
+
[[package]]
4904
+
name = "zstd"
4905
+
version = "0.13.3"
4906
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4907
+
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
4908
+
dependencies = [
4909
+
"zstd-safe",
4910
+
]
4911
+
4912
+
[[package]]
4913
+
name = "zstd-safe"
4914
+
version = "7.2.4"
4915
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4916
+
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
4917
+
dependencies = [
4918
+
"zstd-sys",
4919
+
]
4920
+
4921
+
[[package]]
4922
+
name = "zstd-sys"
4923
+
version = "2.0.15+zstd.1.5.7"
4924
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4925
+
checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237"
4926
+
dependencies = [
4927
+
"cc",
4928
+
"pkg-config",
4929
+
]
+30
-5
Cargo.toml
+30
-5
Cargo.toml
···
1
1
[package]
2
-
name = "pds_bells_and_whistles"
3
-
version = "0.1.0"
2
+
name = "pds_gatekeeper"
3
+
version = "0.1.2"
4
4
edition = "2024"
5
+
license = "MIT"
5
6
6
7
[dependencies]
7
-
axum = { version = "0.7", features = ["macros", "json"] }
8
-
tokio = { version = "1.39", features = ["rt-multi-thread", "macros", "signal"] }
9
-
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite"] }
8
+
axum = { version = "0.8.4", features = ["macros", "json"] }
9
+
tokio = { version = "1.47.1", features = ["rt-multi-thread", "macros", "signal"] }
10
+
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "migrate", "chrono"] }
10
11
dotenvy = "0.15.7"
11
12
serde = { version = "1.0", features = ["derive"] }
12
13
serde_json = "1.0"
13
14
tracing = "0.1"
14
15
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
16
+
hyper-util = { version = "0.1.16", features = ["client", "client-legacy"] }
17
+
tower-http = { version = "0.6", features = ["cors", "compression-zstd"] }
18
+
tower_governor = { version = "0.8.0", features = ["axum", "tracing"] }
19
+
hex = "0.4"
20
+
jwt-compact = { version = "0.8.0", features = ["es256k"] }
21
+
scrypt = "0.11"
22
+
#Leaveing these two cause I think it is needed by the email crate for ssl
23
+
aws-lc-rs = "1.13.0"
24
+
rustls = { version = "0.23", default-features = false, features = ["tls12", "std", "logging", "aws_lc_rs"] }
25
+
lettre = { version = "0.11", default-features = false, features = ["builder", "webpki-roots", "rustls", "aws-lc-rs", "smtp-transport", "tokio1", "tokio1-rustls"] }
26
+
handlebars = { version = "6.3.2", features = ["rust-embed"] }
27
+
rust-embed = "8.7.2"
28
+
axum-template = { version = "3.0.0", features = ["handlebars"] }
29
+
rand = "0.9.2"
30
+
anyhow = "1.0.99"
31
+
chrono = { version = "0.4.42", features = ["default", "serde"] }
32
+
sha2 = "0.10"
33
+
jacquard-common = "0.9.2"
34
+
jacquard-identity = "0.9.2"
35
+
multibase = "0.9.2"
36
+
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
37
+
urlencoding = "2.1"
38
+
html-escape = "0.2.13"
39
+
josekit = "0.10.3"
+10
Dockerfile
+10
Dockerfile
···
1
+
FROM rust:1.89.0-bookworm AS builder
2
+
WORKDIR /app
3
+
COPY ../ /app
4
+
RUN cargo build --release
5
+
#
6
+
FROM rust:1.89-slim-bookworm AS api
7
+
RUN apt-get update
8
+
RUN apt-get install -y ca-certificates
9
+
COPY --from=builder /app/target/release/pds_gatekeeper /usr/local/bin/pds_gatekeeper
10
+
CMD ["pds_gatekeeper"]
+21
LICENSE.md
+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.
+207
README.md
+207
README.md
···
1
+
# PDS gatekeeper
2
+
3
+
A microservice that sits on the same server as the PDS to add some of the security that the entryway does.
4
+
5
+

6
+
7
+
PDS gatekeeper works by overriding some of the PDS endpoints inside your Caddyfile to provide gatekeeping to certain
8
+
endpoints. Mainly, the ability to have 2FA on a self hosted PDS like it does on a Bluesky mushroom(PDS). Most of the
9
+
logic of these endpoints still happens on the PDS via a proxied request, just some are gatekept.
10
+
11
+
# Features
12
+
13
+
## 2FA
14
+
15
+
- Overrides The login endpoint to add 2FA for both Bluesky client logged in and OAuth logins
16
+
- Overrides the settings endpoints as well. As long as you have a confirmed email you can turn on 2FA
17
+
18
+
## Captcha on account creation
19
+
20
+
Require a `verificationCode` set on the `createAccount` request. This is gotten from completing a captcha challenge
21
+
hosted on the
22
+
PDS mimicking what the Bluesky Entryway does. Migration tools will need to support this, but social-apps will support
23
+
and redirect to `GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT`. This is how the clients know to get the code to prove a captcha
24
+
was successful.
25
+
26
+
- Requires `GATEKEEPER_CREATE_ACCOUNT_CAPTCHA` to be set to true.
27
+
- Requires `PDS_HCAPTCHA_SITE_KEY` and `PDS_HCAPTCHA_SECRET_KEY` to be set. Can sign up at https://www.hcaptcha.com/
28
+
- Requires proxying `/xrpc/com.atproto.server.describeServer`, `/xrpc/com.atproto.server.createAccount` and `/gate/*` to
29
+
PDS
30
+
Gatekeeper
31
+
- Optional `GATEKEEPER_JWE_KEY` key to encrypt the captcha verification code. Defaults to a random 32 byte key. Not
32
+
strictly needed unless you're scaling
33
+
- Optional`GATEKEEPER_DEFAULT_CAPTCHA_REDIRECT` default redirect on captcha success. Defaults to `https://bsky.app`.
34
+
- Optional `GATEKEEPER_CAPTCHA_SUCCESS_REDIRECTS` allowed redirect urls for captcha success. You want these to match the
35
+
url showing the captcha. Defaults are:
36
+
- https://bsky.app
37
+
- https://pdsmoover.com
38
+
- https://blacksky.community
39
+
- https://tektite.cc
40
+
41
+
## Block account creation unless it's a migration
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.
48
+
49
+
# Setup
50
+
51
+
PDS Gatekeeper has 2 parts to its setup, docker compose file and a reverse proxy (Caddy in this case). I will be
52
+
assuming you setup the PDS following the directions
53
+
found [here](https://atproto.com/guides/self-hosting), but if yours is different, or you have questions, feel free to
54
+
let
55
+
me know, and we can figure it out.
56
+
57
+
## Docker compose
58
+
59
+
The pds gatekeeper container can be found on docker hub under the name `fatfingers23/pds_gatekeeper`. The container does
60
+
need access to the `/pds` root folder to access the same db's as your PDS. The part you need to add would look a bit
61
+
like below. You can find a full example of what I use for my pds at [./examples/compose.yml](./examples/compose.yml).
62
+
This is usually found at `/pds/compose.yaml`on your PDS>
63
+
64
+
```yml
65
+
gatekeeper:
66
+
container_name: gatekeeper
67
+
image: fatfingers23/pds_gatekeeper:latest
68
+
network_mode: host
69
+
restart: unless-stopped
70
+
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
71
+
volumes:
72
+
- type: bind
73
+
source: /pds
74
+
target: /pds
75
+
depends_on:
76
+
- pds
77
+
```
78
+
79
+
For Coolify, if you're using Traefik as your proxy you'll need to make sure the labels for the container are set up
80
+
correctly. A full example can be found at [./examples/coolify-compose.yml](./examples/coolify-compose.yml).
81
+
82
+
```yml
83
+
gatekeeper:
84
+
container_name: gatekeeper
85
+
image: 'fatfingers23/pds_gatekeeper:latest'
86
+
restart: unless-stopped
87
+
volumes:
88
+
- '/pds:/pds'
89
+
environment:
90
+
- 'PDS_DATA_DIRECTORY=${PDS_DATA_DIRECTORY:-/pds}'
91
+
- 'PDS_BASE_URL=http://pds:3000'
92
+
- GATEKEEPER_HOST=0.0.0.0
93
+
depends_on:
94
+
- pds
95
+
healthcheck:
96
+
test:
97
+
- CMD
98
+
- timeout
99
+
- '1'
100
+
- bash
101
+
- '-c'
102
+
- 'cat < /dev/null > /dev/tcp/0.0.0.0/8080'
103
+
interval: 10s
104
+
timeout: 5s
105
+
retries: 3
106
+
start_period: 10s
107
+
labels:
108
+
- traefik.enable=true
109
+
- 'traefik.http.routers.pds-gatekeeper.rule=Host(`yourpds.com`) && (Path(`/xrpc/com.atproto.server.getSession`) || Path(`/xrpc/com.atproto.server.updateEmail`) || Path(`/xrpc/com.atproto.server.createSession`) || Path(`/xrpc/com.atproto.server.createAccount`) || Path(`/@atproto/oauth-provider/~api/sign-in`))'
110
+
- traefik.http.routers.pds-gatekeeper.entrypoints=https
111
+
- traefik.http.routers.pds-gatekeeper.tls=true
112
+
- traefik.http.routers.pds-gatekeeper.priority=100
113
+
- traefik.http.routers.pds-gatekeeper.middlewares=gatekeeper-cors
114
+
- traefik.http.services.pds-gatekeeper.loadbalancer.server.port=8080
115
+
- traefik.http.services.pds-gatekeeper.loadbalancer.server.scheme=http
116
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowmethods=GET,POST,PUT,DELETE,OPTIONS,PATCH'
117
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowheaders=*'
118
+
- 'traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolalloworiginlist=*'
119
+
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolmaxage=100
120
+
- traefik.http.middlewares.gatekeeper-cors.headers.addvaryheader=true
121
+
- traefik.http.middlewares.gatekeeper-cors.headers.accesscontrolallowcredentials=true
122
+
```
123
+
124
+
## Caddy setup
125
+
126
+
For the reverse proxy I use caddy. This part is what overwrites the endpoints and proxies them to PDS gatekeeper to add
127
+
in extra functionality. The main part is below, for a full example see [./examples/Caddyfile](./examples/Caddyfile).
128
+
This is usually found at `/pds/caddy/etc/caddy/Caddyfile` on your PDS.
129
+
130
+
```
131
+
@gatekeeper {
132
+
path /xrpc/com.atproto.server.getSession
133
+
path /xrpc/com.atproto.server.describeServer
134
+
path /xrpc/com.atproto.server.updateEmail
135
+
path /xrpc/com.atproto.server.createSession
136
+
path /xrpc/com.atproto.server.createAccount
137
+
path /@atproto/oauth-provider/~api/sign-in
138
+
path /gate/*
139
+
}
140
+
141
+
handle @gatekeeper {
142
+
reverse_proxy http://localhost:8080
143
+
}
144
+
145
+
reverse_proxy http://localhost:3000
146
+
```
147
+
148
+
If you use a cloudflare tunnel then your caddyfile would look a bit more like below with your tunnel proxying to
149
+
`localhost:8081` (or w/e port you want).
150
+
151
+
```
152
+
http://*.localhost:8082, http://localhost:8082 {
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
+
}
162
+
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
170
+
}
171
+
172
+
```
173
+
174
+
# Environment variables and bonuses
175
+
176
+
Every environment variable can be set in the `pds.env` and shared between PDS and gatekeeper and the PDS, with the
177
+
exception of `PDS_ENV_LOCATION`. This can be set to load the pds.env, by default it checks `/pds/pds.env` and is
178
+
recommended to mount the `/pds` folder on the server to `/pds` in the pds gatekeeper container.
179
+
180
+
`PDS_DATA_DIRECTORY` - Root directory of the PDS. Same as the one found in `pds.env` this is how pds gatekeeper knows
181
+
knows the rest of the environment variables.
182
+
183
+
`GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY` - The folder for templates of the emails PDS gatekeeper sends. You can find them
184
+
in [./email_templates](./email_templates). You are free to edit them as you please and set this variable to a location
185
+
in the pds gateekeper container and it will use them in place of the default ones. Just make sure ot keep the names the
186
+
same.
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
+
191
+
`PDS_BASE_URL` - Base url of the PDS. You most likely want `https://localhost:3000` which is also the default
192
+
193
+
`GATEKEEPER_HOST` - Host for pds gatekeeper. Defaults to `127.0.0.1`
194
+
195
+
`GATEKEEPER_PORT` - Port for pds gatekeeper. Defaults to `8080`
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
+
+5
build.rs
+5
build.rs
+159
email_templates/two_factor_code.hbs
+159
email_templates/two_factor_code.hbs
···
1
+
<html dir="ltr" lang="en">
2
+
3
+
<head>
4
+
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
5
+
<meta name="x-apple-disable-message-reformatting"/>
6
+
<title>Sign in to Bluesky</title>
7
+
<meta
8
+
name="description"
9
+
content="We received a sign in request for your account."
10
+
/>
11
+
</head>
12
+
<div
13
+
style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0"
14
+
>We received a sign-in request for the account @{{handle}}. Use the code below to sign in
15
+
<div
16
+
>
17
+
ย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟย โโโโโ๏ปฟ
18
+
</div>
19
+
</div>
20
+
21
+
<body
22
+
style="padding:12px;padding-bottom:40px;background-color:hsl(211, 20%, 95.3%)"
23
+
>
24
+
<table
25
+
align="center"
26
+
width="100%"
27
+
border="0"
28
+
cellPadding="0"
29
+
cellSpacing="0"
30
+
role="presentation"
31
+
style="max-width:37.5em"
32
+
>
33
+
<tbody>
34
+
<tr style="width:100%">
35
+
<td>
36
+
<table
37
+
align="center"
38
+
width="100%"
39
+
border="0"
40
+
cellPadding="0"
41
+
cellSpacing="0"
42
+
role="presentation"
43
+
style="padding-top:24px;padding-bottom:24px"
44
+
>
45
+
<tbody>
46
+
<tr>
47
+
<td><img
48
+
alt="Bluesky"
49
+
src="https://bsky.social/about/images/email/email_logo_default.png"
50
+
style="display:block;outline:none;border:none;text-decoration:none;width:110px;margin:0 auto"
51
+
/></td>
52
+
</tr>
53
+
</tbody>
54
+
</table>
55
+
<table
56
+
align="center"
57
+
width="100%"
58
+
border="0"
59
+
cellPadding="0"
60
+
cellSpacing="0"
61
+
role="presentation"
62
+
style="padding:24px;padding-bottom:16px;border-radius:12px;background-color:#FFFFFF"
63
+
>
64
+
<tbody>
65
+
<tr>
66
+
<td>
67
+
<h1
68
+
style="font-size:26px;letter-spacing:0.25px;color:#000000;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0"
69
+
>Sign in to Bluesky</h1>
70
+
<p
71
+
style="font-size:16px;line-height:1.4;margin:0px 0px;letter-spacing:0.25px;color:hsl(211, 24%, 34.2%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;padding-top:12px;padding-bottom:12px"
72
+
>We received a sign-in request for the account @{{handle}}. Use the code below to sign in.</p>
73
+
<code
74
+
style="display:block;padding:16px;border-radius:8px;border-width:1px;border-style:solid;background-color:hsl(211, 20%, 95.3%);border-color:hsl(211, 20%, 85.89999999999999%);font-size:14px;letter-spacing:0.25px;font-family:monospace;text-transform:uppercase"
75
+
>{{token}}</code>
76
+
<p style="font-size:14px;line-height:1.4;margin:0px 0px;letter-spacing:0.25px;color:hsl(211,20%,53%);font-family:-apple-system,BlinkMacSystemFont,'Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif;padding-top:12px">
77
+
If this wasn't you, we recommend taking steps to
78
+
protect your account by
79
+
<a href="https://bsky.app/settings"
80
+
style="color:hsl(211,20%,53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system,BlinkMacSystemFont,'Roboto','Oxygen','Ubuntu','Cantarell','Fira Sans','Droid Sans','Helvetica Neue',sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
81
+
target="_blank"
82
+
data-saferedirecturl="https://bsky.app/settings">changing
83
+
your password.</a></p>
84
+
<table
85
+
align="center"
86
+
width="100%"
87
+
border="0"
88
+
cellPadding="0"
89
+
cellSpacing="0"
90
+
role="presentation"
91
+
style="padding-top:24px"
92
+
>
93
+
<tbody>
94
+
<tr>
95
+
<td>
96
+
<hr
97
+
style="width:100%;border:none;border-top:1px solid #eaeaea;margin:0"
98
+
/>
99
+
<table
100
+
align="center"
101
+
width="100%"
102
+
border="0"
103
+
cellPadding="0"
104
+
cellSpacing="0"
105
+
role="presentation"
106
+
style="padding-top:16px;vertical-align:middle"
107
+
>
108
+
<tbody style="width:100%">
109
+
<tr style="width:100%">
110
+
<td data-id="__react-email-column">
111
+
<p
112
+
style="font-size:14px;line-height:1.4;margin:0px 0px;color:hsl(211, 20%, 53%);font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;letter-spacing:0.25px"
113
+
><a
114
+
href="https://bsky.app"
115
+
style="color:hsl(211, 20%, 53%);text-decoration:none;text-decoration-line:underline;font-family:-apple-system, BlinkMacSystemFont, 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;margin:0px 0px;line-height:1.0;font-size:14px;letter-spacing:0.25px"
116
+
target="_blank"
117
+
>Bluesky</a>, the social internet</p>
118
+
</td>
119
+
<td
120
+
data-id="__react-email-column"
121
+
style="width:24px"
122
+
><img
123
+
alt="๐ฆ"
124
+
src="https://bsky.social/about/images/email/email_mark_dark.png"
125
+
style="display:block;outline:none;border:none;text-decoration:none;width:24px"
126
+
/></td>
127
+
</tr>
128
+
</tbody>
129
+
</table>
130
+
</td>
131
+
</tr>
132
+
</tbody>
133
+
</table>
134
+
</td>
135
+
</tr>
136
+
</tbody>
137
+
</table>
138
+
<table
139
+
align="center"
140
+
width="100%"
141
+
border="0"
142
+
cellPadding="0"
143
+
cellSpacing="0"
144
+
role="presentation"
145
+
style="height:500px"
146
+
>
147
+
<tbody>
148
+
<tr>
149
+
<td></td>
150
+
</tr>
151
+
</tbody>
152
+
</table>
153
+
</td>
154
+
</tr>
155
+
</tbody>
156
+
</table>
157
+
</body>
158
+
159
+
</html>
+30
examples/Caddyfile
+30
examples/Caddyfile
···
1
+
{
2
+
email youremail@myemail.com
3
+
on_demand_tls {
4
+
ask http://localhost:3000/tls-check
5
+
}
6
+
}
7
+
8
+
*.yourpds.com, yourpds.com {
9
+
tls {
10
+
on_demand
11
+
}
12
+
# You'll most likely just want from here to....
13
+
@gatekeeper {
14
+
path /xrpc/com.atproto.server.getSession
15
+
path /xrpc/com.atproto.server.describeServer
16
+
path /xrpc/com.atproto.server.updateEmail
17
+
path /xrpc/com.atproto.server.createSession
18
+
path /xrpc/com.atproto.server.createAccount
19
+
path /@atproto/oauth-provider/~api/sign-in
20
+
path /gate/*
21
+
}
22
+
23
+
handle @gatekeeper {
24
+
#This is the address for PDS gatekeeper, default is 8080
25
+
reverse_proxy http://localhost:8080
26
+
}
27
+
28
+
reverse_proxy http://localhost:3000
29
+
#..here. Copy and paste this replacing the reverse_proxy http://localhost:3000 line
30
+
}
+51
examples/compose.yml
+51
examples/compose.yml
···
1
+
version: '3.9'
2
+
services:
3
+
caddy:
4
+
container_name: caddy
5
+
image: caddy:2
6
+
network_mode: host
7
+
depends_on:
8
+
- pds
9
+
restart: unless-stopped
10
+
volumes:
11
+
- type: bind
12
+
source: /pds/caddy/data
13
+
target: /data
14
+
- type: bind
15
+
source: /pds/caddy/etc/caddy
16
+
target: /etc/caddy
17
+
pds:
18
+
container_name: pds
19
+
image: ghcr.io/bluesky-social/pds:0.4
20
+
network_mode: host
21
+
restart: unless-stopped
22
+
volumes:
23
+
- type: bind
24
+
source: /pds
25
+
target: /pds
26
+
env_file:
27
+
- /pds/pds.env
28
+
watchtower:
29
+
container_name: watchtower
30
+
image: containrrr/watchtower:latest
31
+
network_mode: host
32
+
volumes:
33
+
- type: bind
34
+
source: /var/run/docker.sock
35
+
target: /var/run/docker.sock
36
+
restart: unless-stopped
37
+
environment:
38
+
WATCHTOWER_CLEANUP: true
39
+
WATCHTOWER_SCHEDULE: "@midnight"
40
+
gatekeeper:
41
+
container_name: gatekeeper
42
+
image: fatfingers23/pds_gatekeeper:latest
43
+
network_mode: host
44
+
restart: unless-stopped
45
+
#This gives the container to the access to the PDS folder. Source is the location on your server of that directory
46
+
volumes:
47
+
- type: bind
48
+
source: /pds
49
+
target: /pds
50
+
depends_on:
51
+
- pds
+73
examples/coolify-compose.yml
+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>
images/gate.jpg
images/gate.jpg
This is a binary file and will not be displayed.
+6
justfile
+6
justfile
+6
migrations/20250814232704_two_factor_accounts.sql
+6
migrations/20250814232704_two_factor_accounts.sql
+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
+
}
+687
src/helpers.rs
+687
src/helpers.rs
···
1
+
use crate::AppState;
2
+
use crate::helpers::TokenCheckError::InvalidToken;
3
+
use anyhow::anyhow;
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
+
};
11
+
use axum_template::TemplateEngine;
12
+
use chrono::Utc;
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
+
};
23
+
use rand::Rng;
24
+
use serde::de::DeserializeOwned;
25
+
use serde_json::{Map, Value};
26
+
use sha2::{Digest, Sha256};
27
+
use sqlx::SqlitePool;
28
+
use std::sync::Arc;
29
+
use tracing::{error, log};
30
+
31
+
///Used to generate the email 2fa code
32
+
const UPPERCASE_BASE32_CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
33
+
34
+
/// The result of a proxied call that attempts to parse JSON.
35
+
pub enum ProxiedResult<T> {
36
+
/// Successfully parsed JSON body along with original response headers.
37
+
Parsed { value: T, _headers: HeaderMap },
38
+
/// Could not or should not parse: return the original (or rebuilt) response as-is.
39
+
Passthrough(Response<Body>),
40
+
}
41
+
42
+
/// Proxy the incoming request to the PDS base URL plus the provided path and attempt to parse
43
+
/// the successful response body as JSON into `T`.
44
+
///
45
+
pub async fn proxy_get_json<T>(
46
+
state: &AppState,
47
+
mut req: Request,
48
+
path: &str,
49
+
) -> Result<ProxiedResult<T>, StatusCode>
50
+
where
51
+
T: DeserializeOwned,
52
+
{
53
+
let uri = format!("{}{}", state.app_config.pds_base_url, path);
54
+
*req.uri_mut() = Uri::try_from(uri).map_err(|_| StatusCode::BAD_REQUEST)?;
55
+
56
+
let result = state
57
+
.reverse_proxy_client
58
+
.request(req)
59
+
.await
60
+
.map_err(|_| StatusCode::BAD_REQUEST)?
61
+
.into_response();
62
+
63
+
if result.status() != StatusCode::OK {
64
+
return Ok(ProxiedResult::Passthrough(result));
65
+
}
66
+
67
+
let response_headers = result.headers().clone();
68
+
let body = result.into_body();
69
+
let body_bytes = to_bytes(body, usize::MAX)
70
+
.await
71
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
72
+
73
+
match serde_json::from_slice::<T>(&body_bytes) {
74
+
Ok(value) => Ok(ProxiedResult::Parsed {
75
+
value,
76
+
_headers: response_headers,
77
+
}),
78
+
Err(err) => {
79
+
error!(%err, "failed to parse proxied JSON response; returning original body");
80
+
let mut builder = Response::builder().status(StatusCode::OK);
81
+
if let Some(headers) = builder.headers_mut() {
82
+
*headers = response_headers;
83
+
}
84
+
let resp = builder
85
+
.body(Body::from(body_bytes))
86
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
87
+
Ok(ProxiedResult::Passthrough(resp))
88
+
}
89
+
}
90
+
}
91
+
92
+
/// Build a JSON error response with the required Content-Type header
93
+
/// Content-Type: application/json;charset=utf-8
94
+
/// Body shape: { "error": string, "message": string }
95
+
pub fn json_error_response(
96
+
status: StatusCode,
97
+
error: impl Into<String>,
98
+
message: impl Into<String>,
99
+
) -> Result<Response<Body>, StatusCode> {
100
+
let body_str = match serde_json::to_string(&serde_json::json!({
101
+
"error": error.into(),
102
+
"message": message.into(),
103
+
})) {
104
+
Ok(s) => s,
105
+
Err(_) => return Err(StatusCode::BAD_REQUEST),
106
+
};
107
+
108
+
Response::builder()
109
+
.status(status)
110
+
.header(CONTENT_TYPE, "application/json;charset=utf-8")
111
+
.body(Body::from(body_str))
112
+
.map_err(|_| StatusCode::BAD_REQUEST)
113
+
}
114
+
115
+
/// Build a JSON error response with the required Content-Type header
116
+
/// Content-Type: application/json (oauth endpoint does not like utf ending)
117
+
/// Body shape: { "error": string, "error_description": string }
118
+
pub fn oauth_json_error_response(
119
+
status: StatusCode,
120
+
error: impl Into<String>,
121
+
message: impl Into<String>,
122
+
) -> Result<Response<Body>, StatusCode> {
123
+
let body_str = match serde_json::to_string(&serde_json::json!({
124
+
"error": error.into(),
125
+
"error_description": message.into(),
126
+
})) {
127
+
Ok(s) => s,
128
+
Err(_) => return Err(StatusCode::BAD_REQUEST),
129
+
};
130
+
131
+
Response::builder()
132
+
.status(status)
133
+
.header(CONTENT_TYPE, "application/json")
134
+
.body(Body::from(body_str))
135
+
.map_err(|_| StatusCode::BAD_REQUEST)
136
+
}
137
+
138
+
/// Creates a random token of 10 characters for email 2FA
139
+
pub fn get_random_token() -> String {
140
+
let mut rng = rand::rng();
141
+
142
+
let mut full_code = String::with_capacity(10);
143
+
for _ in 0..10 {
144
+
let idx = rng.random_range(0..UPPERCASE_BASE32_CHARS.len());
145
+
full_code.push(UPPERCASE_BASE32_CHARS[idx] as char);
146
+
}
147
+
148
+
let slice_one = &full_code[0..5];
149
+
let slice_two = &full_code[5..10];
150
+
format!("{slice_one}-{slice_two}")
151
+
}
152
+
153
+
pub enum TokenCheckError {
154
+
InvalidToken,
155
+
ExpiredToken,
156
+
}
157
+
158
+
pub enum AuthResult {
159
+
WrongIdentityOrPassword,
160
+
/// The string here is the email address to create a hint for oauth
161
+
TwoFactorRequired(String),
162
+
/// User does not have 2FA enabled, or using an app password, or passes it
163
+
ProxyThrough,
164
+
TokenCheckFailed(TokenCheckError),
165
+
}
166
+
167
+
pub enum IdentifierType {
168
+
Email,
169
+
Did,
170
+
Handle,
171
+
}
172
+
173
+
impl IdentifierType {
174
+
fn what_is_it(identifier: String) -> Self {
175
+
if identifier.contains("@") {
176
+
IdentifierType::Email
177
+
} else if identifier.contains("did:") {
178
+
IdentifierType::Did
179
+
} else {
180
+
IdentifierType::Handle
181
+
}
182
+
}
183
+
}
184
+
185
+
/// Creates a hex string from the password and salt to find app passwords
186
+
fn scrypt_hex(password: &str, salt: &str) -> anyhow::Result<String> {
187
+
let params = scrypt::Params::new(14, 8, 1, 64)?;
188
+
let mut derived = [0u8; 64];
189
+
scrypt::scrypt(password.as_bytes(), salt.as_bytes(), ¶ms, &mut derived)?;
190
+
Ok(hex::encode(derived))
191
+
}
192
+
193
+
/// Hashes the app password. did is used as the salt.
194
+
pub fn hash_app_password(did: &str, password: &str) -> anyhow::Result<String> {
195
+
let mut hasher = Sha256::new();
196
+
hasher.update(did.as_bytes());
197
+
let sha = hasher.finalize();
198
+
let salt = hex::encode(&sha[..16]);
199
+
let hash_hex = scrypt_hex(password, &salt)?;
200
+
Ok(format!("{salt}:{hash_hex}"))
201
+
}
202
+
203
+
async fn verify_password(password: &str, password_scrypt: &str) -> anyhow::Result<bool> {
204
+
// Expected format: "salt:hash" where hash is hex of scrypt(password, salt, 64 bytes)
205
+
let mut parts = password_scrypt.splitn(2, ':');
206
+
let salt = match parts.next() {
207
+
Some(s) if !s.is_empty() => s,
208
+
_ => return Ok(false),
209
+
};
210
+
let stored_hash_hex = match parts.next() {
211
+
Some(h) if !h.is_empty() => h,
212
+
_ => return Ok(false),
213
+
};
214
+
215
+
// Derive using the shared helper and compare
216
+
let derived_hex = match scrypt_hex(password, salt) {
217
+
Ok(h) => h,
218
+
Err(_) => return Ok(false),
219
+
};
220
+
221
+
Ok(derived_hex.as_str() == stored_hash_hex)
222
+
}
223
+
224
+
/// Handles the auth checks along with sending a 2fa email
225
+
pub async fn preauth_check(
226
+
state: &AppState,
227
+
identifier: &str,
228
+
password: &str,
229
+
two_factor_code: Option<String>,
230
+
oauth: bool,
231
+
) -> anyhow::Result<AuthResult> {
232
+
// Determine identifier type
233
+
let id_type = IdentifierType::what_is_it(identifier.to_string());
234
+
235
+
// Query account DB for did and passwordScrypt based on identifier type
236
+
let account_row: Option<(String, String, String, String)> = match id_type {
237
+
IdentifierType::Email => {
238
+
sqlx::query_as::<_, (String, String, String, String)>(
239
+
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
240
+
FROM actor
241
+
LEFT JOIN account ON actor.did = account.did
242
+
where account.email = ? LIMIT 1",
243
+
)
244
+
.bind(identifier)
245
+
.fetch_optional(&state.account_pool)
246
+
.await?
247
+
}
248
+
IdentifierType::Handle => {
249
+
sqlx::query_as::<_, (String, String, String, String)>(
250
+
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
251
+
FROM actor
252
+
LEFT JOIN account ON actor.did = account.did
253
+
where actor.handle = ? LIMIT 1",
254
+
)
255
+
.bind(identifier)
256
+
.fetch_optional(&state.account_pool)
257
+
.await?
258
+
}
259
+
IdentifierType::Did => {
260
+
sqlx::query_as::<_, (String, String, String, String)>(
261
+
"SELECT account.did, account.passwordScrypt, account.email, actor.handle
262
+
FROM actor
263
+
LEFT JOIN account ON actor.did = account.did
264
+
where account.did = ? LIMIT 1",
265
+
)
266
+
.bind(identifier)
267
+
.fetch_optional(&state.account_pool)
268
+
.await?
269
+
}
270
+
};
271
+
272
+
if let Some((did, password_scrypt, email, handle)) = account_row {
273
+
// Verify password before proceeding to 2FA email step
274
+
let verified = verify_password(password, &password_scrypt).await?;
275
+
if !verified {
276
+
if oauth {
277
+
//OAuth does not allow app password logins so just go ahead and send it along it's way
278
+
return Ok(AuthResult::WrongIdentityOrPassword);
279
+
}
280
+
//Theres a chance it could be an app password so check that as well
281
+
return match verify_app_password(&state.account_pool, &did, password).await {
282
+
Ok(valid) => {
283
+
if valid {
284
+
//Was a valid app password up to the PDS now
285
+
Ok(AuthResult::ProxyThrough)
286
+
} else {
287
+
Ok(AuthResult::WrongIdentityOrPassword)
288
+
}
289
+
}
290
+
Err(err) => {
291
+
log::error!("Error checking the app password: {err}");
292
+
Err(err)
293
+
}
294
+
};
295
+
}
296
+
297
+
// Check two-factor requirement for this DID in the gatekeeper DB
298
+
let required_opt = sqlx::query_as::<_, (u8,)>(
299
+
"SELECT required FROM two_factor_accounts WHERE did = ? LIMIT 1",
300
+
)
301
+
.bind(did.clone())
302
+
.fetch_optional(&state.pds_gatekeeper_pool)
303
+
.await?;
304
+
305
+
let two_factor_required = match required_opt {
306
+
Some(row) => row.0 != 0,
307
+
None => false,
308
+
};
309
+
310
+
if two_factor_required {
311
+
//Two factor is required and a taken was provided
312
+
if let Some(two_factor_code) = two_factor_code {
313
+
//if the two_factor_code is set need to see if we have a valid token
314
+
if !two_factor_code.is_empty() {
315
+
return match assert_valid_token(
316
+
&state.account_pool,
317
+
did.clone(),
318
+
two_factor_code,
319
+
)
320
+
.await
321
+
{
322
+
Ok(_) => {
323
+
let result_of_cleanup =
324
+
delete_all_email_tokens(&state.account_pool, did.clone()).await;
325
+
if result_of_cleanup.is_err() {
326
+
log::error!(
327
+
"There was an error deleting the email tokens after login: {:?}",
328
+
result_of_cleanup.err()
329
+
)
330
+
}
331
+
Ok(AuthResult::ProxyThrough)
332
+
}
333
+
Err(err) => Ok(AuthResult::TokenCheckFailed(err)),
334
+
};
335
+
}
336
+
}
337
+
338
+
return match create_two_factor_token(&state.account_pool, did).await {
339
+
Ok(code) => {
340
+
let mut email_data = Map::new();
341
+
email_data.insert("token".to_string(), Value::from(code.clone()));
342
+
email_data.insert("handle".to_string(), Value::from(handle.clone()));
343
+
let email_body = state
344
+
.template_engine
345
+
.render("two_factor_code.hbs", email_data)?;
346
+
347
+
let email_message = Message::builder()
348
+
//TODO prob get the proper type in the state
349
+
.from(state.app_config.mailer_from.parse()?)
350
+
.to(email.parse()?)
351
+
.subject(&state.app_config.email_subject)
352
+
.multipart(
353
+
MultiPart::alternative() // This is composed of two parts.
354
+
.singlepart(
355
+
SinglePart::builder()
356
+
.header(header::ContentType::TEXT_PLAIN)
357
+
.body(format!("We received a sign-in request for the account @{handle}. Use the code: {code} to sign in. If this wasn't you, we recommend taking steps to protect your account by changing your password at https://bsky.app/settings.")), // Every message should have a plain text fallback.
358
+
)
359
+
.singlepart(
360
+
SinglePart::builder()
361
+
.header(header::ContentType::TEXT_HTML)
362
+
.body(email_body),
363
+
),
364
+
)?;
365
+
match state.mailer.send(email_message).await {
366
+
Ok(_) => Ok(AuthResult::TwoFactorRequired(mask_email(email))),
367
+
Err(err) => {
368
+
log::error!("Error sending the 2FA email: {err}");
369
+
Err(anyhow!(err))
370
+
}
371
+
}
372
+
}
373
+
Err(err) => {
374
+
log::error!("error on creating a 2fa token: {err}");
375
+
Err(anyhow!(err))
376
+
}
377
+
};
378
+
}
379
+
}
380
+
381
+
// No local 2FA requirement (or account not found)
382
+
Ok(AuthResult::ProxyThrough)
383
+
}
384
+
385
+
pub async fn create_two_factor_token(
386
+
account_db: &SqlitePool,
387
+
did: String,
388
+
) -> anyhow::Result<String> {
389
+
let purpose = "2fa_code";
390
+
391
+
let token = get_random_token();
392
+
let right_now = Utc::now();
393
+
394
+
let res = sqlx::query(
395
+
"INSERT INTO email_token (purpose, did, token, requestedAt)
396
+
VALUES (?, ?, ?, ?)
397
+
ON CONFLICT(purpose, did) DO UPDATE SET
398
+
token=excluded.token,
399
+
requestedAt=excluded.requestedAt
400
+
WHERE did=excluded.did",
401
+
)
402
+
.bind(purpose)
403
+
.bind(&did)
404
+
.bind(&token)
405
+
.bind(right_now)
406
+
.execute(account_db)
407
+
.await;
408
+
409
+
match res {
410
+
Ok(_) => Ok(token),
411
+
Err(err) => {
412
+
log::error!("Error creating a two factor token: {err}");
413
+
Err(anyhow::anyhow!(err))
414
+
}
415
+
}
416
+
}
417
+
418
+
pub async fn delete_all_email_tokens(account_db: &SqlitePool, did: String) -> anyhow::Result<()> {
419
+
sqlx::query("DELETE FROM email_token WHERE did = ?")
420
+
.bind(did)
421
+
.execute(account_db)
422
+
.await?;
423
+
Ok(())
424
+
}
425
+
426
+
pub async fn assert_valid_token(
427
+
account_db: &SqlitePool,
428
+
did: String,
429
+
token: String,
430
+
) -> Result<(), TokenCheckError> {
431
+
let token_upper = token.to_ascii_uppercase();
432
+
let purpose = "2fa_code";
433
+
434
+
let row: Option<(String,)> = sqlx::query_as(
435
+
"SELECT requestedAt FROM email_token WHERE purpose = ? AND did = ? AND token = ? LIMIT 1",
436
+
)
437
+
.bind(purpose)
438
+
.bind(did)
439
+
.bind(token_upper)
440
+
.fetch_optional(account_db)
441
+
.await
442
+
.map_err(|err| {
443
+
log::error!("Error getting the 2fa token: {err}");
444
+
InvalidToken
445
+
})?;
446
+
447
+
match row {
448
+
None => Err(InvalidToken),
449
+
Some(row) => {
450
+
// Token lives for 15 minutes
451
+
let expiration_ms = 15 * 60_000;
452
+
453
+
let requested_at_utc = match chrono::DateTime::parse_from_rfc3339(&row.0) {
454
+
Ok(dt) => dt.with_timezone(&Utc),
455
+
Err(_) => {
456
+
return Err(TokenCheckError::InvalidToken);
457
+
}
458
+
};
459
+
460
+
let now = Utc::now();
461
+
let age_ms = (now - requested_at_utc).num_milliseconds();
462
+
let expired = age_ms > expiration_ms;
463
+
if expired {
464
+
return Err(TokenCheckError::ExpiredToken);
465
+
}
466
+
467
+
Ok(())
468
+
}
469
+
}
470
+
}
471
+
472
+
/// We just need to confirm if it's there or not. Will let the PDS do the actual figuring of permissions
473
+
pub async fn verify_app_password(
474
+
account_db: &SqlitePool,
475
+
did: &str,
476
+
password: &str,
477
+
) -> anyhow::Result<bool> {
478
+
let password_scrypt = hash_app_password(did, password)?;
479
+
480
+
let row: Option<(i64,)> = sqlx::query_as(
481
+
"SELECT Count(*) FROM app_password WHERE did = ? AND passwordScrypt = ? LIMIT 1",
482
+
)
483
+
.bind(did)
484
+
.bind(password_scrypt)
485
+
.fetch_optional(account_db)
486
+
.await?;
487
+
488
+
Ok(match row {
489
+
None => false,
490
+
Some((count,)) => count > 0,
491
+
})
492
+
}
493
+
494
+
/// Mask an email address into a hint like "2***0@p***m".
495
+
pub fn mask_email(email: String) -> String {
496
+
// Basic split on first '@'
497
+
let mut parts = email.splitn(2, '@');
498
+
let local = match parts.next() {
499
+
Some(l) => l,
500
+
None => return email.to_string(),
501
+
};
502
+
let domain_rest = match parts.next() {
503
+
Some(d) if !d.is_empty() => d,
504
+
_ => return email.to_string(),
505
+
};
506
+
507
+
// Helper to mask a single label (keep first and last, middle becomes ***).
508
+
fn mask_label(s: &str) -> String {
509
+
let chars: Vec<char> = s.chars().collect();
510
+
match chars.len() {
511
+
0 => String::new(),
512
+
1 => format!("{}***", chars[0]),
513
+
2 => format!("{}***{}", chars[0], chars[1]),
514
+
_ => format!("{}***{}", chars[0], chars[chars.len() - 1]),
515
+
}
516
+
}
517
+
518
+
// Mask local
519
+
let masked_local = mask_label(local);
520
+
521
+
// Mask first domain label only, keep the rest of the domain intact
522
+
let mut dom_parts = domain_rest.splitn(2, '.');
523
+
let first_label = dom_parts.next().unwrap_or("");
524
+
let rest = dom_parts.next();
525
+
let masked_first = mask_label(first_label);
526
+
let masked_domain = if let Some(rest) = rest {
527
+
format!("{}.{rest}", masked_first)
528
+
} else {
529
+
masked_first
530
+
};
531
+
532
+
format!("{masked_local}@{masked_domain}")
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
+
}
+387
-58
src/main.rs
+387
-58
src/main.rs
···
1
+
#![warn(clippy::unwrap_used)]
2
+
use crate::gate::{get_gate, post_gate};
3
+
use crate::oauth_provider::sign_in;
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
+
};
16
+
use axum_template::engine::Engine;
17
+
use handlebars::Handlebars;
18
+
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
19
+
use jacquard_common::types::did::Did;
20
+
use jacquard_identity::{PublicResolver, resolver::PlcSource};
21
+
use lettre::{AsyncSmtpTransport, Tokio1Executor};
22
+
use rand::Rng;
23
+
use rust_embed::RustEmbed;
24
+
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode};
25
+
use sqlx::{SqlitePool, sqlite::SqlitePoolOptions};
26
+
use std::path::Path;
27
+
use std::sync::Arc;
28
+
use std::time::Duration;
1
29
use std::{env, net::SocketAddr};
2
-
use axum::{extract::State, routing::get, Json, Router};
3
-
// use dotenvy::dotenv;
4
-
use serde::Serialize;
5
-
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
6
-
use tracing::{error, info, log};
7
-
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
30
+
use tower_governor::{
31
+
GovernorLayer, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor,
32
+
};
33
+
use tower_http::{
34
+
compression::CompressionLayer,
35
+
cors::{Any, CorsLayer},
36
+
};
37
+
use tracing::log;
38
+
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
8
39
40
+
mod gate;
41
+
pub mod helpers;
42
+
mod middleware;
43
+
mod oauth_provider;
9
44
mod xrpc;
10
45
46
+
type HyperUtilClient = hyper_util::client::legacy::Client<HttpConnector, Body>;
47
+
48
+
#[derive(RustEmbed)]
49
+
#[folder = "email_templates"]
50
+
#[include = "*.hbs"]
51
+
struct EmailTemplates;
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
+
11
148
#[derive(Clone)]
12
-
struct AppState {
13
-
pool: SqlitePool,
149
+
pub struct AppState {
150
+
account_pool: SqlitePool,
151
+
pds_gatekeeper_pool: SqlitePool,
152
+
reverse_proxy_client: HyperUtilClient,
153
+
mailer: AsyncSmtpTransport<Tokio1Executor>,
154
+
template_engine: Engine<Handlebars<'static>>,
155
+
resolver: Arc<PublicResolver>,
156
+
app_config: AppConfig,
14
157
}
15
158
16
-
#[derive(Serialize)]
17
-
struct HealthResponse {
18
-
status: &'static str,
19
-
}
159
+
async fn root_handler() -> impl axum::response::IntoResponse {
160
+
let body = r"
161
+
162
+
...oO _.--X~~OO~~X--._ ...oOO
163
+
_.-~ / \ II / \ ~-._
164
+
[].-~ \ / \||/ \ / ~-.[] ...o
165
+
...o _ ||/ \ / || \ / \|| _
166
+
(_) |X X || X X| (_)
167
+
_-~-_ ||\ / \ || / \ /|| _-~-_
168
+
||||| || \ / \ /||\ / \ / || |||||
169
+
| |_|| \ / \ / || \ / \ / ||_| |
170
+
| |~|| X X || X X ||~| |
171
+
==============| | || / \ / \ || / \ / \ || | |==============
172
+
______________| | || / \ / \||/ \ / \ || | |______________
173
+
. . | | ||/ \ / || \ / \|| | | . .
174
+
/ | | |X X || X X| | | / /
175
+
/ . | | ||\ / \ || / \ /|| | | . / .
176
+
. / | | || \ / \ /||\ / \ / || | | . .
177
+
. . | | || \ / \ / || \ / \ / || | | .
178
+
/ | | || X X || X X || | | . / . /
179
+
/ . | | || / \ / \ || / \ / \ || | | /
180
+
/ | | || / \ / \||/ \ / \ || | | . /
181
+
. . . | | ||/ \ / /||\ \ / \|| | | /. .
182
+
| |_|X X / II \ X X|_| | . . /
183
+
==============| |~II~~~~~~~~~~~~~~OO~~~~~~~~~~~~~~II~| |==============
184
+
";
20
185
21
-
#[derive(Serialize)]
22
-
struct DbPingResponse {
23
-
db: &'static str,
24
-
value: i64,
186
+
let intro = "\n\nThis is a PDS gatekeeper\n\nCode: https://tangled.sh/@baileytownsend.dev/pds-gatekeeper\n";
187
+
188
+
let banner = format!(" {body}\n{intro}");
189
+
190
+
(
191
+
[(header::CONTENT_TYPE, "text/plain; charset=utf-8")],
192
+
banner,
193
+
)
25
194
}
26
195
27
196
#[tokio::main]
28
197
async fn main() -> Result<(), Box<dyn std::error::Error>> {
29
198
setup_tracing();
30
-
//TODO prod
31
-
// dotenvy::from_path(Path::new("/pds.env"))?;
32
-
// let pds_root = env::var("PDS_DATA_DIRECTORY")?;
33
-
let pds_root = "/home/baileytownsend/Documents/code/docker_compose/pds/pds_data";
34
-
let account_db_url = format!("{}/account.sqlite", pds_root);
35
-
log::info!("accounts_db_url: {}", account_db_url);
36
-
let max_connections: u32 = env::var("DATABASE_MAX_CONNECTIONS")
37
-
.ok()
38
-
.and_then(|s| s.parse().ok())
39
-
.unwrap_or(5);
199
+
let pds_env_location =
200
+
env::var("PDS_ENV_LOCATION").unwrap_or_else(|_| "/pds/pds.env".to_string());
201
+
202
+
let result_of_finding_pds_env = dotenvy::from_path(Path::new(&pds_env_location));
203
+
if let Err(e) = result_of_finding_pds_env {
204
+
log::error!(
205
+
"Error loading pds.env file (ignore if you loaded your variables in the environment somehow else): {e}"
206
+
);
207
+
}
208
+
209
+
let pds_root =
210
+
env::var("PDS_DATA_DIRECTORY").expect("PDS_DATA_DIRECTORY is not set in your pds.env file");
211
+
let account_db_url = format!("{pds_root}/account.sqlite");
212
+
213
+
let account_options = SqliteConnectOptions::new()
214
+
.filename(account_db_url)
215
+
.busy_timeout(Duration::from_secs(5));
216
+
217
+
let account_pool = SqlitePoolOptions::new()
218
+
.max_connections(5)
219
+
.connect_with(account_options)
220
+
.await?;
40
221
41
-
//TODO may need to add journal_mode=WAL ?
42
-
let pool = SqlitePoolOptions::new()
43
-
.max_connections(max_connections)
44
-
.connect(&account_db_url)
222
+
let bells_db_url = format!("{pds_root}/pds_gatekeeper.sqlite");
223
+
let options = SqliteConnectOptions::new()
224
+
.journal_mode(SqliteJournalMode::Wal)
225
+
.filename(bells_db_url)
226
+
.create_if_missing(true)
227
+
.busy_timeout(Duration::from_secs(5));
228
+
let pds_gatekeeper_pool = SqlitePoolOptions::new()
229
+
.max_connections(5)
230
+
.connect_with(options)
45
231
.await?;
46
232
47
-
let state = AppState { pool };
233
+
// Run migrations for the extra database
234
+
// Note: the migrations are embedded at compile time from the given directory
235
+
// sqlx
236
+
sqlx::migrate!("./migrations")
237
+
.run(&pds_gatekeeper_pool)
238
+
.await?;
48
239
49
-
let app = Router::new()
50
-
.route("/health", get(health))
51
-
.route("/db/ping", get(db_ping))
52
-
.with_state(state);
240
+
let client: HyperUtilClient =
241
+
hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new())
242
+
.build(HttpConnector::new());
243
+
244
+
//Emailer set up
245
+
let smtp_url =
246
+
env::var("PDS_EMAIL_SMTP_URL").expect("PDS_EMAIL_SMTP_URL is not set in your pds.env file");
247
+
248
+
let mailer: AsyncSmtpTransport<Tokio1Executor> =
249
+
AsyncSmtpTransport::<Tokio1Executor>::from_url(smtp_url.as_str())?.build();
250
+
//Email templates setup
251
+
let mut hbs = Handlebars::new();
252
+
253
+
let users_email_directory = env::var("GATEKEEPER_EMAIL_TEMPLATES_DIRECTORY");
254
+
if let Ok(users_email_directory) = users_email_directory {
255
+
hbs.register_template_file(
256
+
"two_factor_code.hbs",
257
+
format!("{users_email_directory}/two_factor_code.hbs"),
258
+
)?;
259
+
} else {
260
+
let _ = hbs.register_embed_templates::<EmailTemplates>();
261
+
}
262
+
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());
53
273
54
-
let host = env::var("HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
55
-
let port: u16 = env::var("PORT").ok().and_then(|s| s.parse().ok()).unwrap_or(8080);
56
-
let addr: SocketAddr = format!("{host}:{port}").parse().expect("valid socket address");
274
+
let state = AppState {
275
+
account_pool,
276
+
pds_gatekeeper_pool,
277
+
reverse_proxy_client: client,
278
+
mailer,
279
+
template_engine: Engine::from(hbs),
280
+
resolver: Arc::new(resolver),
281
+
app_config: AppConfig::new(),
282
+
};
57
283
58
-
info!(%addr, %account_db_url, "starting server");
284
+
// Rate limiting
285
+
//Allows 5 within 60 seconds, and after 60 should drop one off? So hit 5, then goes to 4 after 60 seconds.
286
+
let captcha_governor_conf = GovernorConfigBuilder::default()
287
+
.per_second(60)
288
+
.burst_size(5)
289
+
.key_extractor(SmartIpKeyExtractor)
290
+
.finish()
291
+
.expect("failed to create governor config for create session. this should not happen and is a bug");
59
292
60
-
let listener = tokio::net::TcpListener::bind(addr).await?;
293
+
// Create a second config with the same settings for the other endpoint
294
+
let sign_in_governor_conf = GovernorConfigBuilder::default()
295
+
.per_second(60)
296
+
.burst_size(5)
297
+
.key_extractor(SmartIpKeyExtractor)
298
+
.finish()
299
+
.expect(
300
+
"failed to create governor config for sign in. this should not happen and is a bug",
301
+
);
61
302
62
-
let server = axum::serve(listener, app).with_graceful_shutdown(shutdown_signal());
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();
63
307
64
-
if let Err(err) = server.await {
65
-
error!(error = %err, "server error");
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);
66
316
}
67
317
68
-
Ok(())
69
-
}
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();
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
+
338
+
let interval = Duration::from_secs(60);
339
+
// a separate background task to clean up
340
+
std::thread::spawn(move || {
341
+
loop {
342
+
std::thread::sleep(interval);
343
+
captcha_governor_limiter.retain_recent();
344
+
sign_in_governor_limiter.retain_recent();
345
+
create_account_governor_limiter.retain_recent();
346
+
}
347
+
});
348
+
349
+
let cors = CorsLayer::new()
350
+
.allow_origin(Any)
351
+
.allow_methods([Method::GET, Method::OPTIONS, Method::POST])
352
+
.allow_headers(Any);
353
+
354
+
let mut app = Router::new()
355
+
.route("/", get(root_handler))
356
+
.route("/xrpc/com.atproto.server.getSession", get(get_session))
357
+
.route(
358
+
"/xrpc/com.atproto.server.describeServer",
359
+
get(describe_server),
360
+
)
361
+
.route(
362
+
"/xrpc/com.atproto.server.updateEmail",
363
+
post(update_email).layer(ax_middleware::from_fn(middleware::extract_did)),
364
+
)
365
+
.route(
366
+
"/@atproto/oauth-provider/~api/sign-in",
367
+
post(sign_in).layer(sign_in_governor_layer.clone()),
368
+
)
369
+
.route(
370
+
"/xrpc/com.atproto.server.createSession",
371
+
post(create_session.layer(sign_in_governor_layer)),
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
386
+
.layer(CompressionLayer::new())
387
+
.layer(cors)
388
+
.with_state(state);
389
+
390
+
let host = env::var("GATEKEEPER_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
391
+
let port: u16 = env::var("GATEKEEPER_PORT")
392
+
.ok()
393
+
.and_then(|s| s.parse().ok())
394
+
.unwrap_or(8080);
395
+
let addr: SocketAddr = format!("{host}:{port}")
396
+
.parse()
397
+
.expect("valid socket address");
398
+
399
+
let listener = tokio::net::TcpListener::bind(addr).await?;
70
400
71
-
async fn health() -> Json<HealthResponse> {
72
-
Json(HealthResponse { status: "ok" })
73
-
}
401
+
let server = axum::serve(
402
+
listener,
403
+
app.into_make_service_with_connect_info::<SocketAddr>(),
404
+
)
405
+
.with_graceful_shutdown(shutdown_signal());
74
406
75
-
async fn db_ping(State(state): State<AppState>) -> Result<Json<DbPingResponse>, axum::http::StatusCode> {
76
-
// Run a DB-agnostic ping that doesn't depend on user tables.
77
-
// In SQLite, SELECT 1 returns a single row with value 1.
78
-
let v: i64 = sqlx::query_scalar("SELECT 1")
79
-
.fetch_one(&state.pool)
80
-
.await
81
-
.map_err(|_| axum::http::StatusCode::SERVICE_UNAVAILABLE)?;
407
+
if let Err(err) = server.await {
408
+
log::error!("server error:{err}");
409
+
}
82
410
83
-
Ok(Json(DbPingResponse { db: "ok", value: v }))
411
+
Ok(())
84
412
}
85
413
86
414
fn setup_tracing() {
···
101
429
102
430
#[cfg(unix)]
103
431
let terminate = async {
104
-
use tokio::signal::unix::{signal, SignalKind};
432
+
use tokio::signal::unix::{SignalKind, signal};
105
433
106
-
let mut sigterm = signal(SignalKind::terminate()).expect("failed to install signal handler");
434
+
let mut sigterm =
435
+
signal(SignalKind::terminate()).expect("failed to install signal handler");
107
436
sigterm.recv().await;
108
437
};
109
438
+116
src/middleware.rs
+116
src/middleware.rs
···
1
+
use crate::helpers::json_error_response;
2
+
use axum::extract::Request;
3
+
use axum::http::{HeaderMap, StatusCode};
4
+
use axum::middleware::Next;
5
+
use axum::response::IntoResponse;
6
+
use jwt_compact::alg::{Hs256, Hs256Key};
7
+
use jwt_compact::{AlgorithmExt, Claims, Token, UntrustedToken, ValidationError};
8
+
use serde::{Deserialize, Serialize};
9
+
use std::env;
10
+
use tracing::log;
11
+
12
+
#[derive(Clone, Debug)]
13
+
pub struct Did(pub Option<String>);
14
+
15
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16
+
pub enum AuthScheme {
17
+
Bearer,
18
+
DPoP,
19
+
}
20
+
21
+
#[derive(Serialize, Deserialize)]
22
+
pub struct TokenClaims {
23
+
pub sub: String,
24
+
}
25
+
26
+
pub async fn extract_did(mut req: Request, next: Next) -> impl IntoResponse {
27
+
let auth = extract_auth(req.headers());
28
+
29
+
match auth {
30
+
Ok(auth_opt) => {
31
+
match auth_opt {
32
+
None => json_error_response(StatusCode::BAD_REQUEST, "TokenRequired", "")
33
+
.expect("Error creating an error response"),
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
+
}
59
+
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
+
}
82
+
}
83
+
84
+
next.run(req).await
85
+
}
86
+
}
87
+
}
88
+
Err(err) => {
89
+
log::error!("Error extracting token: {err}");
90
+
json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "")
91
+
.expect("Error creating an error response")
92
+
}
93
+
}
94
+
}
95
+
96
+
fn extract_auth(headers: &HeaderMap) -> Result<Option<(AuthScheme, String)>, String> {
97
+
match headers.get(axum::http::header::AUTHORIZATION) {
98
+
None => Ok(None),
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
+
}
112
+
}
113
+
}
114
+
}
115
+
}
116
+
}
+139
src/oauth_provider.rs
+139
src/oauth_provider.rs
···
1
+
use crate::AppState;
2
+
use crate::helpers::{AuthResult, oauth_json_error_response, preauth_check};
3
+
use axum::body::Body;
4
+
use axum::extract::State;
5
+
use axum::http::header::CONTENT_TYPE;
6
+
use axum::http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
7
+
use axum::response::{IntoResponse, Response};
8
+
use axum::{Json, extract};
9
+
use serde::{Deserialize, Serialize};
10
+
use tracing::log;
11
+
12
+
#[derive(Serialize, Deserialize, Clone)]
13
+
pub struct SignInRequest {
14
+
pub username: String,
15
+
pub password: String,
16
+
#[serde(skip_serializing_if = "Option::is_none")]
17
+
pub remember: Option<bool>,
18
+
pub locale: String,
19
+
#[serde(skip_serializing_if = "Option::is_none", rename = "emailOtp")]
20
+
pub email_otp: Option<String>,
21
+
}
22
+
23
+
pub async fn sign_in(
24
+
State(state): State<AppState>,
25
+
headers: HeaderMap,
26
+
Json(mut payload): extract::Json<SignInRequest>,
27
+
) -> Result<Response<Body>, StatusCode> {
28
+
let identifier = payload.username.clone();
29
+
let password = payload.password.clone();
30
+
let auth_factor_token = payload.email_otp.clone();
31
+
32
+
match preauth_check(&state, &identifier, &password, auth_factor_token, true).await {
33
+
Ok(result) => match result {
34
+
AuthResult::WrongIdentityOrPassword => oauth_json_error_response(
35
+
StatusCode::BAD_REQUEST,
36
+
"invalid_request",
37
+
"Invalid identifier or password",
38
+
),
39
+
AuthResult::TwoFactorRequired(masked_email) => {
40
+
let body_str = match serde_json::to_string(&serde_json::json!({
41
+
"error": "second_authentication_factor_required",
42
+
"error_description": format!("emailOtp authentication factor required (hint: {})", masked_email),
43
+
"type": "emailOtp",
44
+
"hint": masked_email,
45
+
})) {
46
+
Ok(s) => s,
47
+
Err(_) => return Err(StatusCode::BAD_REQUEST),
48
+
};
49
+
50
+
Response::builder()
51
+
.status(StatusCode::BAD_REQUEST)
52
+
.header(CONTENT_TYPE, "application/json")
53
+
.body(Body::from(body_str))
54
+
.map_err(|_| StatusCode::BAD_REQUEST)
55
+
}
56
+
AuthResult::ProxyThrough => {
57
+
//No 2FA or already passed
58
+
let uri = format!(
59
+
"{}{}",
60
+
state.app_config.pds_base_url, "/@atproto/oauth-provider/~api/sign-in"
61
+
);
62
+
63
+
let mut req = axum::http::Request::post(uri);
64
+
if let Some(req_headers) = req.headers_mut() {
65
+
// Copy headers but remove problematic ones. There was an issue with the PDS not parsing the body fully if i forwarded all headers
66
+
copy_filtered_headers(&headers, req_headers);
67
+
//Setting the content type to application/json manually
68
+
req_headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
69
+
}
70
+
71
+
//Clears the email_otp because the pds will reject a request with it.
72
+
payload.email_otp = None;
73
+
let payload_bytes =
74
+
serde_json::to_vec(&payload).map_err(|_| StatusCode::BAD_REQUEST)?;
75
+
76
+
let req = req
77
+
.body(Body::from(payload_bytes))
78
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
79
+
80
+
let proxied = state
81
+
.reverse_proxy_client
82
+
.request(req)
83
+
.await
84
+
.map_err(|_| StatusCode::BAD_REQUEST)?
85
+
.into_response();
86
+
87
+
Ok(proxied)
88
+
}
89
+
//Ignoring the type of token check failure. Looks like oauth on the entry treads them the same.
90
+
AuthResult::TokenCheckFailed(_) => oauth_json_error_response(
91
+
StatusCode::BAD_REQUEST,
92
+
"invalid_request",
93
+
"Unable to sign-in due to an unexpected server error",
94
+
),
95
+
},
96
+
Err(err) => {
97
+
log::error!(
98
+
"Error during pre-auth check. This happens on the oauth signin endpoint when trying to decide if the user has access:\n {err}"
99
+
);
100
+
oauth_json_error_response(
101
+
StatusCode::BAD_REQUEST,
102
+
"pds_gatekeeper_error",
103
+
"This error was not generated by the PDS, but PDS Gatekeeper. Please contact your PDS administrator for help and for them to review the server logs.",
104
+
)
105
+
}
106
+
}
107
+
}
108
+
109
+
fn is_disallowed_header(name: &HeaderName) -> bool {
110
+
// possible problematic headers with proxying
111
+
matches!(
112
+
name.as_str(),
113
+
"connection"
114
+
| "keep-alive"
115
+
| "proxy-authenticate"
116
+
| "proxy-authorization"
117
+
| "te"
118
+
| "trailer"
119
+
| "transfer-encoding"
120
+
| "upgrade"
121
+
| "host"
122
+
| "content-length"
123
+
| "content-encoding"
124
+
| "expect"
125
+
| "accept-encoding"
126
+
)
127
+
}
128
+
129
+
fn copy_filtered_headers(src: &HeaderMap, dst: &mut HeaderMap) {
130
+
for (name, value) in src.iter() {
131
+
if is_disallowed_header(name) {
132
+
continue;
133
+
}
134
+
// Only copy valid headers
135
+
if let Ok(hv) = HeaderValue::from_bytes(value.as_bytes()) {
136
+
dst.insert(name.clone(), hv);
137
+
}
138
+
}
139
+
}
+597
-17
src/xrpc/com_atproto_server.rs
+597
-17
src/xrpc/com_atproto_server.rs
···
1
+
use crate::AppState;
2
+
use crate::helpers::{
3
+
AuthResult, ProxiedResult, TokenCheckError, VerifyServiceAuthError, json_error_response,
4
+
preauth_check, proxy_get_json, verify_gate_token, verify_service_auth,
5
+
};
6
+
use crate::middleware::Did;
7
+
use axum::body::{Body, to_bytes};
1
8
use axum::extract::State;
2
-
use axum::{extract, Json};
3
-
use serde::Deserialize;
4
-
use crate::{AppState, DbPingResponse};
9
+
use axum::http::{HeaderMap, StatusCode, header};
10
+
use axum::response::{IntoResponse, Response};
11
+
use axum::{Extension, Json, debug_handler, extract, extract::Request};
12
+
use chrono::{Duration, Utc};
13
+
use jacquard_common::types::did::Did as JacquardDid;
14
+
use serde::{Deserialize, Serialize};
15
+
use serde_json;
16
+
use tracing::log;
17
+
18
+
#[derive(Serialize, Deserialize, Debug, Clone)]
19
+
#[serde(rename_all = "camelCase")]
20
+
enum AccountStatus {
21
+
Takendown,
22
+
Suspended,
23
+
Deactivated,
24
+
}
25
+
26
+
#[derive(Serialize, Deserialize, Debug, Clone)]
27
+
#[serde(rename_all = "camelCase")]
28
+
struct GetSessionResponse {
29
+
handle: String,
30
+
did: String,
31
+
#[serde(skip_serializing_if = "Option::is_none")]
32
+
email: Option<String>,
33
+
#[serde(skip_serializing_if = "Option::is_none")]
34
+
email_confirmed: Option<bool>,
35
+
#[serde(skip_serializing_if = "Option::is_none")]
36
+
email_auth_factor: Option<bool>,
37
+
#[serde(skip_serializing_if = "Option::is_none")]
38
+
did_doc: Option<String>,
39
+
#[serde(skip_serializing_if = "Option::is_none")]
40
+
active: Option<bool>,
41
+
#[serde(skip_serializing_if = "Option::is_none")]
42
+
status: Option<AccountStatus>,
43
+
}
5
44
45
+
#[derive(Serialize, Deserialize, Debug, Clone)]
46
+
#[serde(rename_all = "camelCase")]
47
+
pub struct UpdateEmailResponse {
48
+
email: String,
49
+
#[serde(skip_serializing_if = "Option::is_none")]
50
+
email_auth_factor: Option<bool>,
51
+
#[serde(skip_serializing_if = "Option::is_none")]
52
+
token: Option<String>,
53
+
}
6
54
7
-
#[derive(Deserialize)]
8
-
struct CreateSessionRequest {
55
+
#[allow(dead_code)]
56
+
#[derive(Deserialize, Serialize)]
57
+
#[serde(rename_all = "camelCase")]
58
+
pub struct CreateSessionRequest {
9
59
identifier: String,
10
60
password: String,
11
-
#[serde(rename = "authFactorToken")]
12
-
auth_factor_token: String,
13
-
#[serde(rename = "allowTakendown")]
14
-
allow_takendown: bool,
61
+
#[serde(skip_serializing_if = "Option::is_none")]
62
+
auth_factor_token: Option<String>,
63
+
#[serde(skip_serializing_if = "Option::is_none")]
64
+
allow_takendown: Option<bool>,
65
+
}
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
+
118
+
pub async fn create_session(
119
+
State(state): State<AppState>,
120
+
headers: HeaderMap,
121
+
Json(payload): extract::Json<CreateSessionRequest>,
122
+
) -> Result<Response<Body>, StatusCode> {
123
+
let identifier = payload.identifier.clone();
124
+
let password = payload.password.clone();
125
+
let auth_factor_token = payload.auth_factor_token.clone();
126
+
127
+
// Run the shared pre-auth logic to validate and check 2FA requirement
128
+
match preauth_check(&state, &identifier, &password, auth_factor_token, false).await {
129
+
Ok(result) => match result {
130
+
AuthResult::WrongIdentityOrPassword => json_error_response(
131
+
StatusCode::UNAUTHORIZED,
132
+
"AuthenticationRequired",
133
+
"Invalid identifier or password",
134
+
),
135
+
AuthResult::TwoFactorRequired(_) => {
136
+
// Email sending step can be handled here if needed in the future.
137
+
json_error_response(
138
+
StatusCode::UNAUTHORIZED,
139
+
"AuthFactorTokenRequired",
140
+
"A sign in code has been sent to your email address",
141
+
)
142
+
}
143
+
AuthResult::ProxyThrough => {
144
+
//No 2FA or already passed
145
+
let uri = format!(
146
+
"{}{}",
147
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createSession"
148
+
);
149
+
150
+
let mut req = axum::http::Request::post(uri);
151
+
if let Some(req_headers) = req.headers_mut() {
152
+
req_headers.extend(headers.clone());
153
+
}
154
+
155
+
let payload_bytes =
156
+
serde_json::to_vec(&payload).map_err(|_| StatusCode::BAD_REQUEST)?;
157
+
let req = req
158
+
.body(Body::from(payload_bytes))
159
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
160
+
161
+
let proxied = state
162
+
.reverse_proxy_client
163
+
.request(req)
164
+
.await
165
+
.map_err(|_| StatusCode::BAD_REQUEST)?
166
+
.into_response();
167
+
168
+
Ok(proxied)
169
+
}
170
+
AuthResult::TokenCheckFailed(err) => match err {
171
+
TokenCheckError::InvalidToken => {
172
+
json_error_response(StatusCode::BAD_REQUEST, "InvalidToken", "Token is invalid")
173
+
}
174
+
TokenCheckError::ExpiredToken => {
175
+
json_error_response(StatusCode::BAD_REQUEST, "ExpiredToken", "Token is expired")
176
+
}
177
+
},
178
+
},
179
+
Err(err) => {
180
+
log::error!(
181
+
"Error during pre-auth check. This happens on the create_session endpoint when trying to decide if the user has access:\n {err}"
182
+
);
183
+
json_error_response(
184
+
StatusCode::INTERNAL_SERVER_ERROR,
185
+
"InternalServerError",
186
+
"This error was not generated by the PDS, but PDS Gatekeeper. Please contact your PDS administrator for help and for them to review the server logs.",
187
+
)
188
+
}
189
+
}
15
190
}
16
191
17
-
async fn create_session(State(state): State<AppState>, extract::Json(payload): extract::Json<CreateSessionRequest>) -> Result<Json<DbPingResponse>, axum::http::StatusCode> {
18
-
// Run a DB-agnostic ping that doesn't depend on user tables.
19
-
// In SQLite, SELECT 1 returns a single row with value 1.
20
-
let v: i64 = sqlx::query_scalar("SELECT 1")
21
-
.fetch_one(&state.pool)
192
+
#[debug_handler]
193
+
pub async fn update_email(
194
+
State(state): State<AppState>,
195
+
Extension(did): Extension<Did>,
196
+
headers: HeaderMap,
197
+
Json(payload): extract::Json<UpdateEmailResponse>,
198
+
) -> Result<Response<Body>, StatusCode> {
199
+
//If email auth is not set at all it is a update email address
200
+
let email_auth_not_set = payload.email_auth_factor.is_none();
201
+
//If email auth is set it is to either turn on or off 2fa
202
+
let email_auth_update = payload.email_auth_factor.unwrap_or(false);
203
+
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();
207
+
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 = ?",
214
+
)
215
+
.bind(&email)
216
+
.fetch_optional(&state.account_pool)
217
+
.await
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
+
};
225
+
226
+
//Since the email is already confirmed we can enable 2fa
227
+
return match email_confirmed {
228
+
None => Err(StatusCode::BAD_REQUEST),
229
+
Some(did_row) => {
230
+
let _ = sqlx::query(
231
+
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 1) ON CONFLICT(did) DO UPDATE SET required = 1",
232
+
)
233
+
.bind(&did_row.0)
234
+
.execute(&state.pds_gatekeeper_pool)
235
+
.await
236
+
.map_err(|err| {
237
+
log::error!("Error enabling 2FA: {err}");
238
+
StatusCode::BAD_REQUEST
239
+
})?;
240
+
241
+
Ok(StatusCode::OK.into_response())
242
+
}
243
+
};
244
+
}
245
+
246
+
// User wants auth turned off
247
+
if !email_auth_update && !email_auth_not_set {
248
+
//User wants auth turned off and has a token
249
+
if let Some(token) = &payload.token {
250
+
let token_found = match sqlx::query_as::<_, (String,)>(
251
+
"SELECT token FROM email_token WHERE token = ? AND did = ? AND purpose = 'update_email'",
252
+
)
253
+
.bind(token)
254
+
.bind(&did.0)
255
+
.fetch_optional(&state.account_pool)
256
+
.await{
257
+
Ok(token) => token,
258
+
Err(err) => {
259
+
log::error!("Error checking if token is valid: {err}");
260
+
return Err(StatusCode::BAD_REQUEST);
261
+
}
262
+
};
263
+
264
+
return if token_found.is_some() {
265
+
//TODO I think there may be a bug here and need to do some retry logic
266
+
// First try was erroring, seconds was allowing
267
+
match sqlx::query(
268
+
"INSERT INTO two_factor_accounts (did, required) VALUES (?, 0) ON CONFLICT(did) DO UPDATE SET required = 0",
269
+
)
270
+
.bind(&did.0)
271
+
.execute(&state.pds_gatekeeper_pool)
272
+
.await {
273
+
Ok(_) => {}
274
+
Err(err) => {
275
+
log::error!("Error updating email auth: {err}");
276
+
return Err(StatusCode::BAD_REQUEST);
277
+
}
278
+
}
279
+
280
+
Ok(StatusCode::OK.into_response())
281
+
} else {
282
+
Err(StatusCode::BAD_REQUEST)
283
+
};
284
+
}
285
+
}
286
+
}
287
+
// Updating the actual email address by sending it on to the PDS
288
+
let uri = format!(
289
+
"{}{}",
290
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.updateEmail"
291
+
);
292
+
let mut req = axum::http::Request::post(uri);
293
+
if let Some(req_headers) = req.headers_mut() {
294
+
req_headers.extend(headers.clone());
295
+
}
296
+
297
+
let payload_bytes = serde_json::to_vec(&payload).map_err(|_| StatusCode::BAD_REQUEST)?;
298
+
let req = req
299
+
.body(Body::from(payload_bytes))
300
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
301
+
302
+
let proxied = state
303
+
.reverse_proxy_client
304
+
.request(req)
22
305
.await
23
-
.map_err(|_| axum::http::StatusCode::SERVICE_UNAVAILABLE)?;
306
+
.map_err(|_| StatusCode::BAD_REQUEST)?
307
+
.into_response();
308
+
309
+
Ok(proxied)
310
+
}
311
+
312
+
pub async fn get_session(
313
+
State(state): State<AppState>,
314
+
req: Request,
315
+
) -> Result<Response<Body>, StatusCode> {
316
+
match proxy_get_json::<GetSessionResponse>(&state, req, "/xrpc/com.atproto.server.getSession")
317
+
.await?
318
+
{
319
+
ProxiedResult::Parsed {
320
+
value: mut session, ..
321
+
} => {
322
+
let did = session.did.clone();
323
+
let required_opt = sqlx::query_as::<_, (u8,)>(
324
+
"SELECT required FROM two_factor_accounts WHERE did = ? LIMIT 1",
325
+
)
326
+
.bind(&did)
327
+
.fetch_optional(&state.pds_gatekeeper_pool)
328
+
.await
329
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
330
+
331
+
let email_auth_factor = match required_opt {
332
+
Some(row) => row.0 != 0,
333
+
None => false,
334
+
};
335
+
336
+
session.email_auth_factor = Some(email_auth_factor);
337
+
Ok(Json(session).into_response())
338
+
}
339
+
ProxiedResult::Passthrough(resp) => Ok(resp),
340
+
}
341
+
}
342
+
343
+
pub async fn describe_server(
344
+
State(state): State<AppState>,
345
+
req: Request,
346
+
) -> Result<Response<Body>, StatusCode> {
347
+
match proxy_get_json::<DescribeServerResponse>(
348
+
&state,
349
+
req,
350
+
"/xrpc/com.atproto.server.describeServer",
351
+
)
352
+
.await?
353
+
{
354
+
ProxiedResult::Parsed {
355
+
value: mut server_info,
356
+
..
357
+
} => {
358
+
//This signifies the server is configured for captcha verification
359
+
server_info.phone_verification_required = Some(state.app_config.use_captcha);
360
+
Ok(Json(server_info).into_response())
361
+
}
362
+
ProxiedResult::Passthrough(resp) => Ok(resp),
363
+
}
364
+
}
365
+
366
+
/// Verify a gate code matches the handle and is not expired
367
+
async fn verify_gate_code(
368
+
state: &AppState,
369
+
code: &str,
370
+
handle: &str,
371
+
) -> Result<bool, anyhow::Error> {
372
+
// First, decrypt and verify the JWE token
373
+
let payload = match verify_gate_token(code, &state.app_config.gate_jwe_key) {
374
+
Ok(p) => p,
375
+
Err(e) => {
376
+
log::warn!("Failed to decrypt gate token: {}", e);
377
+
return Ok(false);
378
+
}
379
+
};
380
+
381
+
// Verify the handle matches
382
+
if payload.handle != handle {
383
+
log::warn!(
384
+
"Gate code handle mismatch: expected {}, got {}",
385
+
handle,
386
+
payload.handle
387
+
);
388
+
return Ok(false);
389
+
}
390
+
391
+
let created_at = chrono::DateTime::parse_from_rfc3339(&payload.created_at)
392
+
.map_err(|e| anyhow::anyhow!("Failed to parse created_at from token: {}", e))?
393
+
.with_timezone(&Utc);
394
+
395
+
let now = Utc::now();
396
+
let age = now - created_at;
397
+
398
+
// Check if the token is expired (5 minutes)
399
+
if age > Duration::minutes(5) {
400
+
log::warn!("Gate code expired for handle {}", handle);
401
+
return Ok(false);
402
+
}
403
+
404
+
// Verify the token exists in the database (to prevent reuse)
405
+
let row: Option<(String,)> =
406
+
sqlx::query_as("SELECT code FROM gate_codes WHERE code = ? and handle = ? LIMIT 1")
407
+
.bind(code)
408
+
.bind(handle)
409
+
.fetch_optional(&state.pds_gatekeeper_pool)
410
+
.await?;
411
+
412
+
if row.is_none() {
413
+
log::warn!("Gate code not found in database or already used");
414
+
return Ok(false);
415
+
}
416
+
417
+
// Token is valid, delete it so it can't be reused
418
+
//TODO probably also delete expired codes? Will need to do that at some point probably altho the where is on code and handle
419
+
420
+
sqlx::query("DELETE FROM gate_codes WHERE code = ?")
421
+
.bind(code)
422
+
.execute(&state.pds_gatekeeper_pool)
423
+
.await?;
424
+
425
+
Ok(true)
426
+
}
427
+
428
+
pub async fn create_account(
429
+
State(state): State<AppState>,
430
+
req: Request,
431
+
) -> Result<Response<Body>, StatusCode> {
432
+
let headers = req.headers().clone();
433
+
let body_bytes = to_bytes(req.into_body(), usize::MAX)
434
+
.await
435
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
436
+
437
+
// Parse the body to check for verification code
438
+
let account_request: CreateAccountRequest =
439
+
serde_json::from_slice(&body_bytes).map_err(|e| {
440
+
log::error!("Failed to parse create account request: {}", e);
441
+
StatusCode::BAD_REQUEST
442
+
})?;
443
+
444
+
// Check for service auth (migrations) if configured
445
+
if state.app_config.allow_only_migrations {
446
+
// Expect Authorization: Bearer <jwt>
447
+
let auth_header = headers
448
+
.get(header::AUTHORIZATION)
449
+
.and_then(|v| v.to_str().ok())
450
+
.map(str::to_string);
451
+
452
+
let Some(value) = auth_header else {
453
+
log::error!("No Authorization header found in the request");
454
+
return json_error_response(
455
+
StatusCode::UNAUTHORIZED,
456
+
"InvalidAuth",
457
+
"This PDS is configured to only allow accounts created by migrations via this endpoint.",
458
+
);
459
+
};
460
+
461
+
// Ensure Bearer prefix
462
+
let token = value.strip_prefix("Bearer ").unwrap_or("").trim();
463
+
if token.is_empty() {
464
+
log::error!("No Service Auth token found in the Authorization header");
465
+
return json_error_response(
466
+
StatusCode::UNAUTHORIZED,
467
+
"InvalidAuth",
468
+
"This PDS is configured to only allow accounts created by migrations via this endpoint.",
469
+
);
470
+
}
471
+
472
+
// Ensure a non-empty DID was provided when migrations are enabled
473
+
let requested_did_str = match account_request.did.as_deref() {
474
+
Some(s) if !s.trim().is_empty() => s,
475
+
_ => {
476
+
return json_error_response(
477
+
StatusCode::BAD_REQUEST,
478
+
"InvalidRequest",
479
+
"The 'did' field is required when migrations are enforced.",
480
+
);
481
+
}
482
+
};
483
+
484
+
// Parse the DID into the expected type for verification
485
+
let requested_did: JacquardDid<'static> = match requested_did_str.parse() {
486
+
Ok(d) => d,
487
+
Err(e) => {
488
+
log::error!(
489
+
"Invalid DID format provided in createAccount: {} | error: {}",
490
+
requested_did_str,
491
+
e
492
+
);
493
+
return json_error_response(
494
+
StatusCode::BAD_REQUEST,
495
+
"InvalidRequest",
496
+
"The 'did' field is not a valid DID.",
497
+
);
498
+
}
499
+
};
500
+
501
+
let nsid = "com.atproto.server.createAccount".parse().unwrap();
502
+
match verify_service_auth(
503
+
token,
504
+
&nsid,
505
+
state.resolver.clone(),
506
+
&state.app_config.pds_service_did,
507
+
&requested_did,
508
+
)
509
+
.await
510
+
{
511
+
//Just do nothing if it passes so it continues.
512
+
Ok(_) => {}
513
+
Err(err) => match err {
514
+
VerifyServiceAuthError::AuthFailed => {
515
+
return json_error_response(
516
+
StatusCode::UNAUTHORIZED,
517
+
"InvalidAuth",
518
+
"This PDS is configured to only allow accounts created by migrations via this endpoint.",
519
+
);
520
+
}
521
+
VerifyServiceAuthError::Error(err) => {
522
+
log::error!("Error verifying service auth token: {err}");
523
+
return json_error_response(
524
+
StatusCode::BAD_REQUEST,
525
+
"InvalidRequest",
526
+
"There has been an error, please contact your PDS administrator for help and for them to review the server logs.",
527
+
);
528
+
}
529
+
},
530
+
}
531
+
}
532
+
533
+
// Check for captcha verification if configured
534
+
if state.app_config.use_captcha {
535
+
if let Some(ref verification_code) = account_request.verification_code {
536
+
match verify_gate_code(&state, verification_code, &account_request.handle).await {
537
+
//TODO has a few errors to support
538
+
539
+
//expired token
540
+
// {
541
+
// "error": "ExpiredToken",
542
+
// "message": "Token has expired"
543
+
// }
24
544
25
-
Ok(Json(DbPingResponse { db: "ok", value: v }))
26
-
}
545
+
//TODO ALSO add rate limits on the /gate endpoints so they can't be abused
546
+
Ok(true) => {
547
+
log::info!("Gate code verified for handle: {}", account_request.handle);
548
+
}
549
+
Ok(false) => {
550
+
log::warn!(
551
+
"Invalid or expired gate code for handle: {}",
552
+
account_request.handle
553
+
);
554
+
return json_error_response(
555
+
StatusCode::BAD_REQUEST,
556
+
"InvalidToken",
557
+
"Token could not be verified",
558
+
);
559
+
}
560
+
Err(e) => {
561
+
log::error!("Error verifying gate code: {}", e);
562
+
return json_error_response(
563
+
StatusCode::INTERNAL_SERVER_ERROR,
564
+
"InvalidToken",
565
+
"Token could not be verified",
566
+
);
567
+
}
568
+
}
569
+
} else {
570
+
// No verification code provided but captcha is required
571
+
log::warn!(
572
+
"No verification code provided for account creation: {}",
573
+
account_request.handle
574
+
);
575
+
return json_error_response(
576
+
StatusCode::BAD_REQUEST,
577
+
"InvalidRequest",
578
+
"Verification is now required on this server.",
579
+
);
580
+
}
581
+
}
582
+
583
+
// Rebuild the request with the same body and headers
584
+
let uri = format!(
585
+
"{}{}",
586
+
state.app_config.pds_base_url, "/xrpc/com.atproto.server.createAccount"
587
+
);
588
+
589
+
let mut new_req = axum::http::Request::post(&uri);
590
+
if let Some(req_headers) = new_req.headers_mut() {
591
+
*req_headers = headers;
592
+
}
593
+
594
+
let new_req = new_req
595
+
.body(Body::from(body_bytes))
596
+
.map_err(|_| StatusCode::BAD_REQUEST)?;
597
+
598
+
let proxied = state
599
+
.reverse_proxy_client
600
+
.request(new_req)
601
+
.await
602
+
.map_err(|_| StatusCode::BAD_REQUEST)?
603
+
.into_response();
604
+
605
+
Ok(proxied)
606
+
}