crates
jacquard-common
jacquard-oauth
jacquard-repo
···
12
12
]
13
13
14
14
[[package]]
15
15
+
name = "adler"
16
16
+
version = "1.0.2"
17
17
+
source = "registry+https://github.com/rust-lang/crates.io-index"
18
18
+
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19
19
+
20
20
+
[[package]]
15
21
name = "adler2"
16
22
version = "2.0.1"
17
23
source = "registry+https://github.com/rust-lang/crates.io-index"
18
24
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
19
25
20
26
[[package]]
27
27
+
name = "adler32"
28
28
+
version = "1.2.0"
29
29
+
source = "registry+https://github.com/rust-lang/crates.io-index"
30
30
+
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
31
31
+
32
32
+
[[package]]
21
33
name = "aho-corasick"
22
34
version = "1.1.4"
23
35
source = "registry+https://github.com/rust-lang/crates.io-index"
···
33
45
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
34
46
35
47
[[package]]
48
48
+
name = "aligned"
49
49
+
version = "0.4.3"
50
50
+
source = "registry+https://github.com/rust-lang/crates.io-index"
51
51
+
checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
52
52
+
dependencies = [
53
53
+
"as-slice",
54
54
+
]
55
55
+
56
56
+
[[package]]
57
57
+
name = "aligned-vec"
58
58
+
version = "0.6.4"
59
59
+
source = "registry+https://github.com/rust-lang/crates.io-index"
60
60
+
checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b"
61
61
+
dependencies = [
62
62
+
"equator",
63
63
+
]
64
64
+
65
65
+
[[package]]
66
66
+
name = "alloc-no-stdlib"
67
67
+
version = "2.0.4"
68
68
+
source = "registry+https://github.com/rust-lang/crates.io-index"
69
69
+
checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
70
70
+
71
71
+
[[package]]
72
72
+
name = "alloc-stdlib"
73
73
+
version = "0.2.2"
74
74
+
source = "registry+https://github.com/rust-lang/crates.io-index"
75
75
+
checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
76
76
+
dependencies = [
77
77
+
"alloc-no-stdlib",
78
78
+
]
79
79
+
80
80
+
[[package]]
36
81
name = "allocator-api2"
37
82
version = "0.2.21"
38
83
source = "registry+https://github.com/rust-lang/crates.io-index"
···
45
90
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
46
91
dependencies = [
47
92
"libc",
93
93
+
]
94
94
+
95
95
+
[[package]]
96
96
+
name = "ansi_colours"
97
97
+
version = "1.2.3"
98
98
+
source = "registry+https://github.com/rust-lang/crates.io-index"
99
99
+
checksum = "14eec43e0298190790f41679fe69ef7a829d2a2ddd78c8c00339e84710e435fe"
100
100
+
dependencies = [
101
101
+
"rgb",
48
102
]
49
103
50
104
[[package]]
···
104
158
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
105
159
106
160
[[package]]
161
161
+
name = "arbitrary"
162
162
+
version = "1.4.2"
163
163
+
source = "registry+https://github.com/rust-lang/crates.io-index"
164
164
+
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
165
165
+
166
166
+
[[package]]
167
167
+
name = "arg_enum_proc_macro"
168
168
+
version = "0.3.4"
169
169
+
source = "registry+https://github.com/rust-lang/crates.io-index"
170
170
+
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
171
171
+
dependencies = [
172
172
+
"proc-macro2",
173
173
+
"quote",
174
174
+
"syn",
175
175
+
]
176
176
+
177
177
+
[[package]]
178
178
+
name = "arrayvec"
179
179
+
version = "0.5.2"
180
180
+
source = "registry+https://github.com/rust-lang/crates.io-index"
181
181
+
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
182
182
+
183
183
+
[[package]]
184
184
+
name = "arrayvec"
185
185
+
version = "0.7.6"
186
186
+
source = "registry+https://github.com/rust-lang/crates.io-index"
187
187
+
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
188
188
+
189
189
+
[[package]]
190
190
+
name = "as-slice"
191
191
+
version = "0.2.1"
192
192
+
source = "registry+https://github.com/rust-lang/crates.io-index"
193
193
+
checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
194
194
+
dependencies = [
195
195
+
"stable_deref_trait",
196
196
+
]
197
197
+
198
198
+
[[package]]
199
199
+
name = "ascii"
200
200
+
version = "1.1.0"
201
201
+
source = "registry+https://github.com/rust-lang/crates.io-index"
202
202
+
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
203
203
+
204
204
+
[[package]]
107
205
name = "async-compression"
108
206
version = "0.4.41"
109
207
source = "registry+https://github.com/rust-lang/crates.io-index"
···
148
246
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
149
247
150
248
[[package]]
249
249
+
name = "av-scenechange"
250
250
+
version = "0.14.1"
251
251
+
source = "registry+https://github.com/rust-lang/crates.io-index"
252
252
+
checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
253
253
+
dependencies = [
254
254
+
"aligned",
255
255
+
"anyhow",
256
256
+
"arg_enum_proc_macro",
257
257
+
"arrayvec 0.7.6",
258
258
+
"log",
259
259
+
"num-rational",
260
260
+
"num-traits",
261
261
+
"pastey",
262
262
+
"rayon",
263
263
+
"thiserror 2.0.18",
264
264
+
"v_frame",
265
265
+
"y4m",
266
266
+
]
267
267
+
268
268
+
[[package]]
269
269
+
name = "av1-grain"
270
270
+
version = "0.2.5"
271
271
+
source = "registry+https://github.com/rust-lang/crates.io-index"
272
272
+
checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
273
273
+
dependencies = [
274
274
+
"anyhow",
275
275
+
"arrayvec 0.7.6",
276
276
+
"log",
277
277
+
"nom",
278
278
+
"num-rational",
279
279
+
"v_frame",
280
280
+
]
281
281
+
282
282
+
[[package]]
283
283
+
name = "avif-serialize"
284
284
+
version = "0.8.8"
285
285
+
source = "registry+https://github.com/rust-lang/crates.io-index"
286
286
+
checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d"
287
287
+
dependencies = [
288
288
+
"arrayvec 0.7.6",
289
289
+
]
290
290
+
291
291
+
[[package]]
292
292
+
name = "axum"
293
293
+
version = "0.8.8"
294
294
+
source = "registry+https://github.com/rust-lang/crates.io-index"
295
295
+
checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8"
296
296
+
dependencies = [
297
297
+
"axum-core",
298
298
+
"bytes",
299
299
+
"form_urlencoded",
300
300
+
"futures-util",
301
301
+
"http",
302
302
+
"http-body",
303
303
+
"http-body-util",
304
304
+
"hyper",
305
305
+
"hyper-util",
306
306
+
"itoa",
307
307
+
"matchit",
308
308
+
"memchr",
309
309
+
"mime",
310
310
+
"percent-encoding",
311
311
+
"pin-project-lite",
312
312
+
"serde_core",
313
313
+
"serde_json",
314
314
+
"serde_path_to_error",
315
315
+
"serde_urlencoded",
316
316
+
"sync_wrapper",
317
317
+
"tokio",
318
318
+
"tower",
319
319
+
"tower-layer",
320
320
+
"tower-service",
321
321
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
322
322
+
]
323
323
+
324
324
+
[[package]]
325
325
+
name = "axum-core"
326
326
+
version = "0.5.6"
327
327
+
source = "registry+https://github.com/rust-lang/crates.io-index"
328
328
+
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
329
329
+
dependencies = [
330
330
+
"bytes",
331
331
+
"futures-core",
332
332
+
"http",
333
333
+
"http-body",
334
334
+
"http-body-util",
335
335
+
"mime",
336
336
+
"pin-project-lite",
337
337
+
"sync_wrapper",
338
338
+
"tower-layer",
339
339
+
"tower-service",
340
340
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
341
341
+
]
342
342
+
343
343
+
[[package]]
344
344
+
name = "axum-macros"
345
345
+
version = "0.5.0"
346
346
+
source = "registry+https://github.com/rust-lang/crates.io-index"
347
347
+
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
348
348
+
dependencies = [
349
349
+
"proc-macro2",
350
350
+
"quote",
351
351
+
"syn",
352
352
+
]
353
353
+
354
354
+
[[package]]
355
355
+
name = "axum-test"
356
356
+
version = "18.7.0"
357
357
+
source = "registry+https://github.com/rust-lang/crates.io-index"
358
358
+
checksum = "0ce2a8627e8d8851f894696b39f2b67807d6375c177361d376173ace306a21e2"
359
359
+
dependencies = [
360
360
+
"anyhow",
361
361
+
"axum",
362
362
+
"bytes",
363
363
+
"bytesize",
364
364
+
"cookie",
365
365
+
"expect-json",
366
366
+
"http",
367
367
+
"http-body-util",
368
368
+
"hyper",
369
369
+
"hyper-util",
370
370
+
"mime",
371
371
+
"pretty_assertions",
372
372
+
"reserve-port",
373
373
+
"rust-multipart-rfc7578_2",
374
374
+
"serde",
375
375
+
"serde_json",
376
376
+
"serde_urlencoded",
377
377
+
"smallvec",
378
378
+
"tokio",
379
379
+
"tower",
380
380
+
"url",
381
381
+
]
382
382
+
383
383
+
[[package]]
151
384
name = "backtrace"
152
385
version = "0.3.76"
153
386
source = "registry+https://github.com/rust-lang/crates.io-index"
···
156
389
"addr2line",
157
390
"cfg-if",
158
391
"libc",
159
159
-
"miniz_oxide",
392
392
+
"miniz_oxide 0.8.9",
160
393
"object",
161
394
"rustc-demangle",
162
395
"windows-link",
···
195
428
196
429
[[package]]
197
430
name = "base64"
431
431
+
version = "0.13.1"
432
432
+
source = "registry+https://github.com/rust-lang/crates.io-index"
433
433
+
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
434
434
+
435
435
+
[[package]]
436
436
+
name = "base64"
198
437
version = "0.22.1"
199
438
source = "registry+https://github.com/rust-lang/crates.io-index"
200
439
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
···
206
445
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
207
446
208
447
[[package]]
448
448
+
name = "bit-set"
449
449
+
version = "0.8.0"
450
450
+
source = "registry+https://github.com/rust-lang/crates.io-index"
451
451
+
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
452
452
+
dependencies = [
453
453
+
"bit-vec",
454
454
+
]
455
455
+
456
456
+
[[package]]
457
457
+
name = "bit-vec"
458
458
+
version = "0.8.0"
459
459
+
source = "registry+https://github.com/rust-lang/crates.io-index"
460
460
+
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
461
461
+
462
462
+
[[package]]
463
463
+
name = "bit_field"
464
464
+
version = "0.10.3"
465
465
+
source = "registry+https://github.com/rust-lang/crates.io-index"
466
466
+
checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
467
467
+
468
468
+
[[package]]
209
469
name = "bitflags"
210
470
version = "2.11.0"
211
471
source = "registry+https://github.com/rust-lang/crates.io-index"
212
472
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
213
473
214
474
[[package]]
475
475
+
name = "bitstream-io"
476
476
+
version = "4.9.0"
477
477
+
source = "registry+https://github.com/rust-lang/crates.io-index"
478
478
+
checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
479
479
+
dependencies = [
480
480
+
"core2",
481
481
+
]
482
482
+
483
483
+
[[package]]
215
484
name = "block-buffer"
216
485
version = "0.10.4"
217
486
source = "registry+https://github.com/rust-lang/crates.io-index"
···
262
531
]
263
532
264
533
[[package]]
534
534
+
name = "brotli"
535
535
+
version = "3.5.0"
536
536
+
source = "registry+https://github.com/rust-lang/crates.io-index"
537
537
+
checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391"
538
538
+
dependencies = [
539
539
+
"alloc-no-stdlib",
540
540
+
"alloc-stdlib",
541
541
+
"brotli-decompressor",
542
542
+
]
543
543
+
544
544
+
[[package]]
545
545
+
name = "brotli-decompressor"
546
546
+
version = "2.5.1"
547
547
+
source = "registry+https://github.com/rust-lang/crates.io-index"
548
548
+
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
549
549
+
dependencies = [
550
550
+
"alloc-no-stdlib",
551
551
+
"alloc-stdlib",
552
552
+
]
553
553
+
554
554
+
[[package]]
555
555
+
name = "buf_redux"
556
556
+
version = "0.8.4"
557
557
+
source = "registry+https://github.com/rust-lang/crates.io-index"
558
558
+
checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
559
559
+
dependencies = [
560
560
+
"memchr",
561
561
+
"safemem",
562
562
+
]
563
563
+
564
564
+
[[package]]
565
565
+
name = "buffer"
566
566
+
version = "0.1.9"
567
567
+
source = "registry+https://github.com/rust-lang/crates.io-index"
568
568
+
checksum = "aab7228d32b5d95be40adeba1d9461c8547b5dadf5f9cbfba09b6d578991df28"
569
569
+
dependencies = [
570
570
+
"arrayvec 0.5.2",
571
571
+
"mac 0.0.2",
572
572
+
]
573
573
+
574
574
+
[[package]]
575
575
+
name = "built"
576
576
+
version = "0.8.0"
577
577
+
source = "registry+https://github.com/rust-lang/crates.io-index"
578
578
+
checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
579
579
+
580
580
+
[[package]]
265
581
name = "bumpalo"
266
582
version = "3.20.2"
267
583
source = "registry+https://github.com/rust-lang/crates.io-index"
268
584
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
269
585
270
586
[[package]]
587
587
+
name = "bytemuck"
588
588
+
version = "1.25.0"
589
589
+
source = "registry+https://github.com/rust-lang/crates.io-index"
590
590
+
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
591
591
+
592
592
+
[[package]]
271
593
name = "byteorder"
272
594
version = "1.5.0"
273
595
source = "registry+https://github.com/rust-lang/crates.io-index"
274
596
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
275
597
276
598
[[package]]
599
599
+
name = "byteorder-lite"
600
600
+
version = "0.1.0"
601
601
+
source = "registry+https://github.com/rust-lang/crates.io-index"
602
602
+
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
603
603
+
604
604
+
[[package]]
277
605
name = "bytes"
278
606
version = "1.11.1"
279
607
source = "registry+https://github.com/rust-lang/crates.io-index"
···
281
609
dependencies = [
282
610
"serde",
283
611
]
612
612
+
613
613
+
[[package]]
614
614
+
name = "bytesize"
615
615
+
version = "2.3.1"
616
616
+
source = "registry+https://github.com/rust-lang/crates.io-index"
617
617
+
checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3"
284
618
285
619
[[package]]
286
620
name = "cast"
···
336
670
]
337
671
338
672
[[package]]
673
673
+
name = "chunked_transfer"
674
674
+
version = "1.5.0"
675
675
+
source = "registry+https://github.com/rust-lang/crates.io-index"
676
676
+
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
677
677
+
678
678
+
[[package]]
339
679
name = "ciborium"
340
680
version = "0.2.2"
341
681
source = "registry+https://github.com/rust-lang/crates.io-index"
···
373
713
"multihash",
374
714
"serde",
375
715
"serde_bytes",
376
376
-
"unsigned-varint",
716
716
+
"unsigned-varint 0.8.0",
377
717
]
378
718
379
719
[[package]]
···
445
785
]
446
786
447
787
[[package]]
788
788
+
name = "color_quant"
789
789
+
version = "1.1.0"
790
790
+
source = "registry+https://github.com/rust-lang/crates.io-index"
791
791
+
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
792
792
+
793
793
+
[[package]]
448
794
name = "colorchoice"
449
795
version = "1.0.5"
450
796
source = "registry+https://github.com/rust-lang/crates.io-index"
451
797
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
452
798
453
799
[[package]]
800
800
+
name = "combine"
801
801
+
version = "4.6.7"
802
802
+
source = "registry+https://github.com/rust-lang/crates.io-index"
803
803
+
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
804
804
+
dependencies = [
805
805
+
"bytes",
806
806
+
"memchr",
807
807
+
]
808
808
+
809
809
+
[[package]]
454
810
name = "compression-codecs"
455
811
version = "0.4.37"
456
812
source = "registry+https://github.com/rust-lang/crates.io-index"
···
466
822
version = "0.4.31"
467
823
source = "registry+https://github.com/rust-lang/crates.io-index"
468
824
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
825
825
+
826
826
+
[[package]]
827
827
+
name = "console"
828
828
+
version = "0.15.11"
829
829
+
source = "registry+https://github.com/rust-lang/crates.io-index"
830
830
+
checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8"
831
831
+
dependencies = [
832
832
+
"encode_unicode",
833
833
+
"libc",
834
834
+
"once_cell",
835
835
+
"windows-sys 0.59.0",
836
836
+
]
469
837
470
838
[[package]]
471
839
name = "const-oid"
···
480
848
checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3"
481
849
482
850
[[package]]
851
851
+
name = "cookie"
852
852
+
version = "0.18.1"
853
853
+
source = "registry+https://github.com/rust-lang/crates.io-index"
854
854
+
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
855
855
+
dependencies = [
856
856
+
"time",
857
857
+
"version_check",
858
858
+
]
859
859
+
860
860
+
[[package]]
483
861
name = "cordyceps"
484
862
version = "0.3.4"
485
863
source = "registry+https://github.com/rust-lang/crates.io-index"
486
864
checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a"
487
865
dependencies = [
488
866
"loom",
489
489
-
"tracing",
867
867
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
490
868
]
491
869
492
870
[[package]]
···
558
936
]
559
937
560
938
[[package]]
939
939
+
name = "crossbeam-deque"
940
940
+
version = "0.8.6"
941
941
+
source = "registry+https://github.com/rust-lang/crates.io-index"
942
942
+
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
943
943
+
dependencies = [
944
944
+
"crossbeam-epoch",
945
945
+
"crossbeam-utils",
946
946
+
]
947
947
+
948
948
+
[[package]]
949
949
+
name = "crossbeam-epoch"
950
950
+
version = "0.9.18"
951
951
+
source = "registry+https://github.com/rust-lang/crates.io-index"
952
952
+
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
953
953
+
dependencies = [
954
954
+
"crossbeam-utils",
955
955
+
]
956
956
+
957
957
+
[[package]]
561
958
name = "crossbeam-utils"
562
959
version = "0.8.21"
563
960
source = "registry+https://github.com/rust-lang/crates.io-index"
564
961
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
565
962
566
963
[[package]]
964
964
+
name = "crossterm"
965
965
+
version = "0.28.1"
966
966
+
source = "registry+https://github.com/rust-lang/crates.io-index"
967
967
+
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
968
968
+
dependencies = [
969
969
+
"bitflags",
970
970
+
"crossterm_winapi",
971
971
+
"parking_lot",
972
972
+
"rustix 0.38.44",
973
973
+
"winapi",
974
974
+
]
975
975
+
976
976
+
[[package]]
977
977
+
name = "crossterm_winapi"
978
978
+
version = "0.9.1"
979
979
+
source = "registry+https://github.com/rust-lang/crates.io-index"
980
980
+
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
981
981
+
dependencies = [
982
982
+
"winapi",
983
983
+
]
984
984
+
985
985
+
[[package]]
567
986
name = "crunchy"
568
987
version = "0.2.4"
569
988
source = "registry+https://github.com/rust-lang/crates.io-index"
···
604
1023
"fiat-crypto",
605
1024
"rustc_version",
606
1025
"subtle",
1026
1026
+
"zeroize",
607
1027
]
608
1028
609
1029
[[package]]
···
692
1112
]
693
1113
694
1114
[[package]]
1115
1115
+
name = "deflate"
1116
1116
+
version = "1.0.0"
1117
1117
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1118
1118
+
checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
1119
1119
+
dependencies = [
1120
1120
+
"adler32",
1121
1121
+
"gzip-header",
1122
1122
+
]
1123
1123
+
1124
1124
+
[[package]]
695
1125
name = "der"
696
1126
version = "0.7.10"
697
1127
source = "registry+https://github.com/rust-lang/crates.io-index"
···
739
1169
checksum = "ab03c107fafeb3ee9f5925686dbb7a73bc76e3932abb0d2b365cb64b169cf04c"
740
1170
741
1171
[[package]]
1172
1172
+
name = "diff"
1173
1173
+
version = "0.1.13"
1174
1174
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1175
1175
+
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
1176
1176
+
1177
1177
+
[[package]]
742
1178
name = "digest"
743
1179
version = "0.10.7"
744
1180
source = "registry+https://github.com/rust-lang/crates.io-index"
···
793
1229
dependencies = [
794
1230
"curve25519-dalek",
795
1231
"ed25519",
1232
1232
+
"rand_core 0.6.4",
1233
1233
+
"serde",
796
1234
"sha2",
797
1235
"subtle",
1236
1236
+
"zeroize",
798
1237
]
799
1238
800
1239
[[package]]
1240
1240
+
name = "either"
1241
1241
+
version = "1.15.0"
1242
1242
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1243
1243
+
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
1244
1244
+
1245
1245
+
[[package]]
801
1246
name = "elliptic-curve"
802
1247
version = "0.13.8"
803
1248
source = "registry+https://github.com/rust-lang/crates.io-index"
···
809
1254
"ff",
810
1255
"generic-array",
811
1256
"group",
1257
1257
+
"hkdf",
812
1258
"pem-rfc7468",
813
1259
"pkcs8",
814
1260
"rand_core 0.6.4",
···
818
1264
]
819
1265
820
1266
[[package]]
1267
1267
+
name = "email_address"
1268
1268
+
version = "0.2.9"
1269
1269
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1270
1270
+
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
1271
1271
+
dependencies = [
1272
1272
+
"serde",
1273
1273
+
]
1274
1274
+
1275
1275
+
[[package]]
821
1276
name = "embedded-io"
822
1277
version = "0.4.0"
823
1278
source = "registry+https://github.com/rust-lang/crates.io-index"
···
830
1285
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
831
1286
832
1287
[[package]]
1288
1288
+
name = "embedded-io-adapters"
1289
1289
+
version = "0.6.2"
1290
1290
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1291
1291
+
checksum = "90ccf22c3feffc79593914c0b4be9a2ed6b11e44cf1f84fd6b77d2ee92de0077"
1292
1292
+
dependencies = [
1293
1293
+
"embedded-io 0.6.1",
1294
1294
+
"embedded-io-async",
1295
1295
+
"tokio",
1296
1296
+
]
1297
1297
+
1298
1298
+
[[package]]
1299
1299
+
name = "embedded-io-async"
1300
1300
+
version = "0.6.1"
1301
1301
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1302
1302
+
checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f"
1303
1303
+
dependencies = [
1304
1304
+
"embedded-io 0.6.1",
1305
1305
+
]
1306
1306
+
1307
1307
+
[[package]]
1308
1308
+
name = "encode_unicode"
1309
1309
+
version = "1.0.0"
1310
1310
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1311
1311
+
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
1312
1312
+
1313
1313
+
[[package]]
833
1314
name = "encoding_rs"
834
1315
version = "0.8.35"
835
1316
source = "registry+https://github.com/rust-lang/crates.io-index"
···
851
1332
]
852
1333
853
1334
[[package]]
1335
1335
+
name = "equator"
1336
1336
+
version = "0.4.2"
1337
1337
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1338
1338
+
checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc"
1339
1339
+
dependencies = [
1340
1340
+
"equator-macro",
1341
1341
+
]
1342
1342
+
1343
1343
+
[[package]]
1344
1344
+
name = "equator-macro"
1345
1345
+
version = "0.4.2"
1346
1346
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1347
1347
+
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
1348
1348
+
dependencies = [
1349
1349
+
"proc-macro2",
1350
1350
+
"quote",
1351
1351
+
"syn",
1352
1352
+
]
1353
1353
+
1354
1354
+
[[package]]
854
1355
name = "equivalent"
855
1356
version = "1.0.2"
856
1357
source = "registry+https://github.com/rust-lang/crates.io-index"
857
1358
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
858
1359
859
1360
[[package]]
1361
1361
+
name = "erased-serde"
1362
1362
+
version = "0.4.10"
1363
1363
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1364
1364
+
checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
1365
1365
+
dependencies = [
1366
1366
+
"serde",
1367
1367
+
"serde_core",
1368
1368
+
"typeid",
1369
1369
+
]
1370
1370
+
1371
1371
+
[[package]]
860
1372
name = "errno"
861
1373
version = "0.3.14"
862
1374
source = "registry+https://github.com/rust-lang/crates.io-index"
···
867
1379
]
868
1380
869
1381
[[package]]
1382
1382
+
name = "expect-json"
1383
1383
+
version = "1.10.1"
1384
1384
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1385
1385
+
checksum = "869f97f4abe8e78fc812a94ad6b721d72c4fb5532877c79610f2c238d7ccf6c4"
1386
1386
+
dependencies = [
1387
1387
+
"chrono",
1388
1388
+
"email_address",
1389
1389
+
"expect-json-macros",
1390
1390
+
"num",
1391
1391
+
"regex",
1392
1392
+
"serde",
1393
1393
+
"serde_json",
1394
1394
+
"thiserror 2.0.18",
1395
1395
+
"typetag",
1396
1396
+
"uuid",
1397
1397
+
]
1398
1398
+
1399
1399
+
[[package]]
1400
1400
+
name = "expect-json-macros"
1401
1401
+
version = "1.10.1"
1402
1402
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1403
1403
+
checksum = "6e6fdf550180a6c29a28cb9aac262dc0064c25735641d2317f670075e9a469d9"
1404
1404
+
dependencies = [
1405
1405
+
"proc-macro2",
1406
1406
+
"quote",
1407
1407
+
"syn",
1408
1408
+
]
1409
1409
+
1410
1410
+
[[package]]
1411
1411
+
name = "exr"
1412
1412
+
version = "1.74.0"
1413
1413
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1414
1414
+
checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
1415
1415
+
dependencies = [
1416
1416
+
"bit_field",
1417
1417
+
"half",
1418
1418
+
"lebe",
1419
1419
+
"miniz_oxide 0.8.9",
1420
1420
+
"rayon-core",
1421
1421
+
"smallvec",
1422
1422
+
"zune-inflate",
1423
1423
+
]
1424
1424
+
1425
1425
+
[[package]]
870
1426
name = "fastrand"
871
1427
version = "2.3.0"
872
1428
source = "registry+https://github.com/rust-lang/crates.io-index"
873
1429
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
874
1430
875
1431
[[package]]
1432
1432
+
name = "fax"
1433
1433
+
version = "0.2.6"
1434
1434
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1435
1435
+
checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
1436
1436
+
dependencies = [
1437
1437
+
"fax_derive",
1438
1438
+
]
1439
1439
+
1440
1440
+
[[package]]
1441
1441
+
name = "fax_derive"
1442
1442
+
version = "0.2.0"
1443
1443
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1444
1444
+
checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
1445
1445
+
dependencies = [
1446
1446
+
"proc-macro2",
1447
1447
+
"quote",
1448
1448
+
"syn",
1449
1449
+
]
1450
1450
+
1451
1451
+
[[package]]
1452
1452
+
name = "fdeflate"
1453
1453
+
version = "0.3.7"
1454
1454
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1455
1455
+
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
1456
1456
+
dependencies = [
1457
1457
+
"simd-adler32",
1458
1458
+
]
1459
1459
+
1460
1460
+
[[package]]
876
1461
name = "ff"
877
1462
version = "0.13.1"
878
1463
source = "registry+https://github.com/rust-lang/crates.io-index"
···
889
1474
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
890
1475
891
1476
[[package]]
1477
1477
+
name = "filetime"
1478
1478
+
version = "0.2.27"
1479
1479
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1480
1480
+
checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db"
1481
1481
+
dependencies = [
1482
1482
+
"cfg-if",
1483
1483
+
"libc",
1484
1484
+
"libredox",
1485
1485
+
]
1486
1486
+
1487
1487
+
[[package]]
892
1488
name = "find-msvc-tools"
893
1489
version = "0.1.9"
894
1490
source = "registry+https://github.com/rust-lang/crates.io-index"
···
901
1497
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
902
1498
dependencies = [
903
1499
"crc32fast",
904
904
-
"miniz_oxide",
1500
1500
+
"miniz_oxide 0.8.9",
905
1501
]
906
1502
907
1503
[[package]]
···
937
1533
]
938
1534
939
1535
[[package]]
1536
1536
+
name = "futf"
1537
1537
+
version = "0.1.5"
1538
1538
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1539
1539
+
checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843"
1540
1540
+
dependencies = [
1541
1541
+
"mac 0.1.1",
1542
1542
+
"new_debug_unreachable",
1543
1543
+
]
1544
1544
+
1545
1545
+
[[package]]
940
1546
name = "futures"
941
1547
version = "0.3.32"
942
1548
source = "registry+https://github.com/rust-lang/crates.io-index"
···
944
1550
dependencies = [
945
1551
"futures-channel",
946
1552
"futures-core",
1553
1553
+
"futures-executor",
947
1554
"futures-io",
948
1555
"futures-sink",
949
1556
"futures-task",
···
980
1587
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
981
1588
982
1589
[[package]]
1590
1590
+
name = "futures-executor"
1591
1591
+
version = "0.3.32"
1592
1592
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1593
1593
+
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
1594
1594
+
dependencies = [
1595
1595
+
"futures-core",
1596
1596
+
"futures-task",
1597
1597
+
"futures-util",
1598
1598
+
]
1599
1599
+
1600
1600
+
[[package]]
983
1601
name = "futures-io"
984
1602
version = "0.3.32"
985
1603
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1027
1645
source = "registry+https://github.com/rust-lang/crates.io-index"
1028
1646
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
1029
1647
dependencies = [
1648
1648
+
"futures-channel",
1030
1649
"futures-core",
1031
1650
"futures-io",
1032
1651
"futures-macro",
···
1091
1710
]
1092
1711
1093
1712
[[package]]
1713
1713
+
name = "gif"
1714
1714
+
version = "0.14.1"
1715
1715
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1716
1716
+
checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
1717
1717
+
dependencies = [
1718
1718
+
"color_quant",
1719
1719
+
"weezl",
1720
1720
+
]
1721
1721
+
1722
1722
+
[[package]]
1094
1723
name = "gimli"
1095
1724
version = "0.32.3"
1096
1725
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1103
1732
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
1104
1733
1105
1734
[[package]]
1735
1735
+
name = "gloo-storage"
1736
1736
+
version = "0.3.0"
1737
1737
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1738
1738
+
checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a"
1739
1739
+
dependencies = [
1740
1740
+
"gloo-utils",
1741
1741
+
"js-sys",
1742
1742
+
"serde",
1743
1743
+
"serde_json",
1744
1744
+
"thiserror 1.0.69",
1745
1745
+
"wasm-bindgen",
1746
1746
+
"web-sys",
1747
1747
+
]
1748
1748
+
1749
1749
+
[[package]]
1750
1750
+
name = "gloo-utils"
1751
1751
+
version = "0.2.0"
1752
1752
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1753
1753
+
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
1754
1754
+
dependencies = [
1755
1755
+
"js-sys",
1756
1756
+
"serde",
1757
1757
+
"serde_json",
1758
1758
+
"wasm-bindgen",
1759
1759
+
"web-sys",
1760
1760
+
]
1761
1761
+
1762
1762
+
[[package]]
1106
1763
name = "group"
1107
1764
version = "0.13.0"
1108
1765
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1111
1768
"ff",
1112
1769
"rand_core 0.6.4",
1113
1770
"subtle",
1771
1771
+
]
1772
1772
+
1773
1773
+
[[package]]
1774
1774
+
name = "gzip-header"
1775
1775
+
version = "1.0.0"
1776
1776
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1777
1777
+
checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2"
1778
1778
+
dependencies = [
1779
1779
+
"crc32fast",
1114
1780
]
1115
1781
1116
1782
[[package]]
···
1129
1795
"slab",
1130
1796
"tokio",
1131
1797
"tokio-util",
1132
1132
-
"tracing",
1798
1798
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
1133
1799
]
1134
1800
1135
1801
[[package]]
···
1153
1819
]
1154
1820
1155
1821
[[package]]
1822
1822
+
name = "hash32"
1823
1823
+
version = "0.3.1"
1824
1824
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1825
1825
+
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
1826
1826
+
dependencies = [
1827
1827
+
"byteorder",
1828
1828
+
]
1829
1829
+
1830
1830
+
[[package]]
1156
1831
name = "hashbrown"
1157
1832
version = "0.14.5"
1158
1833
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1182
1857
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
1183
1858
dependencies = [
1184
1859
"atomic-polyfill",
1185
1185
-
"hash32",
1860
1860
+
"hash32 0.2.1",
1186
1861
"rustc_version",
1187
1862
"serde",
1188
1863
"spin 0.9.8",
···
1190
1865
]
1191
1866
1192
1867
[[package]]
1868
1868
+
name = "heapless"
1869
1869
+
version = "0.9.2"
1870
1870
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1871
1871
+
checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed"
1872
1872
+
dependencies = [
1873
1873
+
"hash32 0.3.1",
1874
1874
+
"stable_deref_trait",
1875
1875
+
]
1876
1876
+
1877
1877
+
[[package]]
1193
1878
name = "heck"
1194
1879
version = "0.4.1"
1195
1880
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1202
1887
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
1203
1888
1204
1889
[[package]]
1890
1890
+
name = "hermit-abi"
1891
1891
+
version = "0.5.2"
1892
1892
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1893
1893
+
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
1894
1894
+
1895
1895
+
[[package]]
1205
1896
name = "hex"
1206
1897
version = "0.4.3"
1207
1898
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1227
1918
"thiserror 1.0.69",
1228
1919
"tinyvec",
1229
1920
"tokio",
1230
1230
-
"tracing",
1921
1921
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
1231
1922
"url",
1232
1923
]
1233
1924
···
1249
1940
"smallvec",
1250
1941
"thiserror 1.0.69",
1251
1942
"tokio",
1252
1252
-
"tracing",
1943
1943
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
1944
1944
+
]
1945
1945
+
1946
1946
+
[[package]]
1947
1947
+
name = "hkdf"
1948
1948
+
version = "0.12.4"
1949
1949
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1950
1950
+
checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7"
1951
1951
+
dependencies = [
1952
1952
+
"hmac",
1253
1953
]
1254
1954
1255
1955
[[package]]
···
1262
1962
]
1263
1963
1264
1964
[[package]]
1965
1965
+
name = "html5ever"
1966
1966
+
version = "0.27.0"
1967
1967
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1968
1968
+
checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4"
1969
1969
+
dependencies = [
1970
1970
+
"log",
1971
1971
+
"mac 0.1.1",
1972
1972
+
"markup5ever",
1973
1973
+
"proc-macro2",
1974
1974
+
"quote",
1975
1975
+
"syn",
1976
1976
+
]
1977
1977
+
1978
1978
+
[[package]]
1265
1979
name = "http"
1266
1980
version = "1.4.0"
1267
1981
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1301
2015
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
1302
2016
1303
2017
[[package]]
2018
2018
+
name = "httpdate"
2019
2019
+
version = "1.0.3"
2020
2020
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2021
2021
+
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
2022
2022
+
2023
2023
+
[[package]]
1304
2024
name = "hyper"
1305
2025
version = "1.8.1"
1306
2026
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1314
2034
"http",
1315
2035
"http-body",
1316
2036
"httparse",
2037
2037
+
"httpdate",
1317
2038
"itoa",
1318
2039
"pin-project-lite",
1319
2040
"pin-utils",
···
1345
2066
source = "registry+https://github.com/rust-lang/crates.io-index"
1346
2067
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
1347
2068
dependencies = [
1348
1348
-
"base64",
2069
2069
+
"base64 0.22.1",
1349
2070
"bytes",
1350
2071
"futures-channel",
1351
2072
"futures-util",
···
1360
2081
"system-configuration",
1361
2082
"tokio",
1362
2083
"tower-service",
1363
1363
-
"tracing",
2084
2084
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
1364
2085
"windows-registry",
1365
2086
]
1366
2087
···
1497
2218
]
1498
2219
1499
2220
[[package]]
2221
2221
+
name = "image"
2222
2222
+
version = "0.25.10"
2223
2223
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2224
2224
+
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
2225
2225
+
dependencies = [
2226
2226
+
"bytemuck",
2227
2227
+
"byteorder-lite",
2228
2228
+
"color_quant",
2229
2229
+
"exr",
2230
2230
+
"gif",
2231
2231
+
"image-webp",
2232
2232
+
"moxcms",
2233
2233
+
"num-traits",
2234
2234
+
"png",
2235
2235
+
"qoi",
2236
2236
+
"ravif",
2237
2237
+
"rayon",
2238
2238
+
"rgb",
2239
2239
+
"tiff 0.11.3",
2240
2240
+
"zune-core",
2241
2241
+
"zune-jpeg",
2242
2242
+
]
2243
2243
+
2244
2244
+
[[package]]
2245
2245
+
name = "image-webp"
2246
2246
+
version = "0.2.4"
2247
2247
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2248
2248
+
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
2249
2249
+
dependencies = [
2250
2250
+
"byteorder-lite",
2251
2251
+
"quick-error 2.0.1",
2252
2252
+
]
2253
2253
+
2254
2254
+
[[package]]
2255
2255
+
name = "imgref"
2256
2256
+
version = "1.12.0"
2257
2257
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2258
2258
+
checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
2259
2259
+
2260
2260
+
[[package]]
1500
2261
name = "indexmap"
1501
2262
version = "2.13.0"
1502
2263
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1504
2265
dependencies = [
1505
2266
"equivalent",
1506
2267
"hashbrown 0.16.1",
2268
2268
+
]
2269
2269
+
2270
2270
+
[[package]]
2271
2271
+
name = "interpolate_name"
2272
2272
+
version = "0.2.4"
2273
2273
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2274
2274
+
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
2275
2275
+
dependencies = [
2276
2276
+
"proc-macro2",
2277
2277
+
"quote",
2278
2278
+
"syn",
1507
2279
]
1508
2280
1509
2281
[[package]]
···
1552
2324
dependencies = [
1553
2325
"memchr",
1554
2326
"serde",
2327
2327
+
]
2328
2328
+
2329
2329
+
[[package]]
2330
2330
+
name = "iroh-car"
2331
2331
+
version = "0.5.1"
2332
2332
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2333
2333
+
checksum = "cb7f8cd4cb9aa083fba8b52e921764252d0b4dcb1cd6d120b809dbfe1106e81a"
2334
2334
+
dependencies = [
2335
2335
+
"anyhow",
2336
2336
+
"cid",
2337
2337
+
"futures",
2338
2338
+
"serde",
2339
2339
+
"serde_ipld_dagcbor",
2340
2340
+
"thiserror 1.0.69",
2341
2341
+
"tokio",
2342
2342
+
"unsigned-varint 0.7.2",
1555
2343
]
1556
2344
1557
2345
[[package]]
···
1567
2355
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
1568
2356
1569
2357
[[package]]
2358
2358
+
name = "itertools"
2359
2359
+
version = "0.14.0"
2360
2360
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2361
2361
+
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
2362
2362
+
dependencies = [
2363
2363
+
"either",
2364
2364
+
]
2365
2365
+
2366
2366
+
[[package]]
1570
2367
name = "itoa"
1571
2368
version = "1.0.18"
1572
2369
source = "registry+https://github.com/rust-lang/crates.io-index"
1573
2370
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
1574
2371
1575
2372
[[package]]
2373
2373
+
name = "jacquard"
2374
2374
+
version = "0.11.0"
2375
2375
+
dependencies = [
2376
2376
+
"bytes",
2377
2377
+
"clap",
2378
2378
+
"getrandom 0.2.17",
2379
2379
+
"gloo-storage",
2380
2380
+
"http",
2381
2381
+
"image",
2382
2382
+
"jacquard-api",
2383
2383
+
"jacquard-common",
2384
2384
+
"jacquard-derive",
2385
2385
+
"jacquard-identity",
2386
2386
+
"jacquard-oauth",
2387
2387
+
"jose-jwk",
2388
2388
+
"miette",
2389
2389
+
"n0-future",
2390
2390
+
"regex",
2391
2391
+
"regex-lite",
2392
2392
+
"reqwest",
2393
2393
+
"serde",
2394
2394
+
"serde_html_form",
2395
2395
+
"serde_json",
2396
2396
+
"smol_str",
2397
2397
+
"thiserror 2.0.18",
2398
2398
+
"tiff 0.6.1",
2399
2399
+
"tokio",
2400
2400
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2401
2401
+
"trait-variant",
2402
2402
+
"viuer",
2403
2403
+
"webpage",
2404
2404
+
]
2405
2405
+
2406
2406
+
[[package]]
1576
2407
name = "jacquard-api"
1577
2408
version = "0.11.1"
1578
2409
dependencies = [
···
1585
2416
]
1586
2417
1587
2418
[[package]]
2419
2419
+
name = "jacquard-axum"
2420
2420
+
version = "0.11.0"
2421
2421
+
dependencies = [
2422
2422
+
"axum",
2423
2423
+
"axum-macros",
2424
2424
+
"axum-test",
2425
2425
+
"base64 0.22.1",
2426
2426
+
"bytes",
2427
2427
+
"chrono",
2428
2428
+
"jacquard",
2429
2429
+
"jacquard-common",
2430
2430
+
"jacquard-derive",
2431
2431
+
"jacquard-identity",
2432
2432
+
"k256",
2433
2433
+
"miette",
2434
2434
+
"multibase",
2435
2435
+
"rand 0.8.5",
2436
2436
+
"reqwest",
2437
2437
+
"serde",
2438
2438
+
"serde_html_form",
2439
2439
+
"serde_json",
2440
2440
+
"thiserror 2.0.18",
2441
2441
+
"tokio",
2442
2442
+
"tower",
2443
2443
+
"tower-http",
2444
2444
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2445
2445
+
"tracing-subscriber 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
2446
2446
+
]
2447
2447
+
2448
2448
+
[[package]]
2449
2449
+
name = "jacquard-codegen-tests"
2450
2450
+
version = "0.0.0"
2451
2451
+
dependencies = [
2452
2452
+
"jacquard-common",
2453
2453
+
"jacquard-derive",
2454
2454
+
"jacquard-lexicon",
2455
2455
+
"miette",
2456
2456
+
"serde",
2457
2457
+
"serde_ipld_dagcbor",
2458
2458
+
"serde_json",
2459
2459
+
"smol_str",
2460
2460
+
"thiserror 2.0.18",
2461
2461
+
]
2462
2462
+
2463
2463
+
[[package]]
1588
2464
name = "jacquard-common"
1589
2465
version = "0.11.0"
1590
2466
dependencies = [
1591
1591
-
"base64",
2467
2467
+
"base64 0.22.1",
1592
2468
"bon",
1593
2469
"bytes",
1594
2470
"chrono",
···
1605
2481
"http",
1606
2482
"ipld-core",
1607
2483
"k256",
1608
1608
-
"maitake-sync",
2484
2484
+
"maitake-sync 0.1.2",
1609
2485
"miette",
1610
2486
"multibase",
1611
2487
"multihash",
···
1633
2509
"tokio",
1634
2510
"tokio-tungstenite-wasm",
1635
2511
"tokio-util",
1636
1636
-
"tracing",
2512
2512
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
1637
2513
"trait-variant",
1638
2514
"unicode-segmentation",
1639
2515
"zstd",
···
1674
2550
"serde_json",
1675
2551
"thiserror 2.0.18",
1676
2552
"tokio",
1677
1677
-
"tracing",
2553
2553
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
1678
2554
"trait-variant",
1679
2555
]
1680
2556
···
1734
2610
]
1735
2611
1736
2612
[[package]]
2613
2613
+
name = "jacquard-oauth"
2614
2614
+
version = "0.11.0"
2615
2615
+
dependencies = [
2616
2616
+
"base64 0.22.1",
2617
2617
+
"bytes",
2618
2618
+
"chrono",
2619
2619
+
"dashmap",
2620
2620
+
"ed25519-dalek",
2621
2621
+
"elliptic-curve",
2622
2622
+
"http",
2623
2623
+
"jacquard-common",
2624
2624
+
"jacquard-identity",
2625
2625
+
"jose-jwa",
2626
2626
+
"jose-jwk",
2627
2627
+
"k256",
2628
2628
+
"miette",
2629
2629
+
"n0-future",
2630
2630
+
"p256",
2631
2631
+
"p384",
2632
2632
+
"rand 0.8.5",
2633
2633
+
"rouille",
2634
2634
+
"serde",
2635
2635
+
"serde_html_form",
2636
2636
+
"serde_json",
2637
2637
+
"sha2",
2638
2638
+
"smol_str",
2639
2639
+
"thiserror 2.0.18",
2640
2640
+
"tokio",
2641
2641
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2642
2642
+
"trait-variant",
2643
2643
+
"webbrowser",
2644
2644
+
]
2645
2645
+
2646
2646
+
[[package]]
2647
2647
+
name = "jacquard-repo"
2648
2648
+
version = "0.11.0"
2649
2649
+
dependencies = [
2650
2650
+
"anyhow",
2651
2651
+
"bytes",
2652
2652
+
"cid",
2653
2653
+
"ed25519-dalek",
2654
2654
+
"hex",
2655
2655
+
"iroh-car",
2656
2656
+
"jacquard-api",
2657
2657
+
"jacquard-common",
2658
2658
+
"jacquard-derive",
2659
2659
+
"k256",
2660
2660
+
"miette",
2661
2661
+
"multihash",
2662
2662
+
"n0-future",
2663
2663
+
"p256",
2664
2664
+
"rand 0.8.5",
2665
2665
+
"serde",
2666
2666
+
"serde_bytes",
2667
2667
+
"serde_ipld_dagcbor",
2668
2668
+
"serde_ipld_dagjson",
2669
2669
+
"serde_json",
2670
2670
+
"sha2",
2671
2671
+
"smol_str",
2672
2672
+
"tempfile",
2673
2673
+
"thiserror 2.0.18",
2674
2674
+
"tokio",
2675
2675
+
"trait-variant",
2676
2676
+
]
2677
2677
+
2678
2678
+
[[package]]
2679
2679
+
name = "jni"
2680
2680
+
version = "0.22.4"
2681
2681
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2682
2682
+
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
2683
2683
+
dependencies = [
2684
2684
+
"cfg-if",
2685
2685
+
"combine",
2686
2686
+
"jni-macros",
2687
2687
+
"jni-sys",
2688
2688
+
"log",
2689
2689
+
"simd_cesu8",
2690
2690
+
"thiserror 2.0.18",
2691
2691
+
"walkdir",
2692
2692
+
"windows-link",
2693
2693
+
]
2694
2694
+
2695
2695
+
[[package]]
2696
2696
+
name = "jni-macros"
2697
2697
+
version = "0.22.4"
2698
2698
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2699
2699
+
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
2700
2700
+
dependencies = [
2701
2701
+
"proc-macro2",
2702
2702
+
"quote",
2703
2703
+
"rustc_version",
2704
2704
+
"simd_cesu8",
2705
2705
+
"syn",
2706
2706
+
]
2707
2707
+
2708
2708
+
[[package]]
2709
2709
+
name = "jni-sys"
2710
2710
+
version = "0.4.1"
2711
2711
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2712
2712
+
checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
2713
2713
+
dependencies = [
2714
2714
+
"jni-sys-macros",
2715
2715
+
]
2716
2716
+
2717
2717
+
[[package]]
2718
2718
+
name = "jni-sys-macros"
2719
2719
+
version = "0.4.1"
2720
2720
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2721
2721
+
checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
2722
2722
+
dependencies = [
2723
2723
+
"quote",
2724
2724
+
"syn",
2725
2725
+
]
2726
2726
+
2727
2727
+
[[package]]
1737
2728
name = "jobserver"
1738
2729
version = "0.1.34"
1739
2730
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1744
2735
]
1745
2736
1746
2737
[[package]]
2738
2738
+
name = "jose-b64"
2739
2739
+
version = "0.1.2"
2740
2740
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2741
2741
+
checksum = "bec69375368709666b21c76965ce67549f2d2db7605f1f8707d17c9656801b56"
2742
2742
+
dependencies = [
2743
2743
+
"base64ct",
2744
2744
+
"serde",
2745
2745
+
"subtle",
2746
2746
+
"zeroize",
2747
2747
+
]
2748
2748
+
2749
2749
+
[[package]]
2750
2750
+
name = "jose-jwa"
2751
2751
+
version = "0.1.2"
2752
2752
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2753
2753
+
checksum = "9ab78e053fe886a351d67cf0d194c000f9d0dcb92906eb34d853d7e758a4b3a7"
2754
2754
+
dependencies = [
2755
2755
+
"serde",
2756
2756
+
]
2757
2757
+
2758
2758
+
[[package]]
2759
2759
+
name = "jose-jwk"
2760
2760
+
version = "0.1.2"
2761
2761
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2762
2762
+
checksum = "280fa263807fe0782ecb6f2baadc28dffc04e00558a58e33bfdb801d11fd58e7"
2763
2763
+
dependencies = [
2764
2764
+
"jose-b64",
2765
2765
+
"jose-jwa",
2766
2766
+
"p256",
2767
2767
+
"p384",
2768
2768
+
"rsa",
2769
2769
+
"serde",
2770
2770
+
"zeroize",
2771
2771
+
]
2772
2772
+
2773
2773
+
[[package]]
2774
2774
+
name = "jpeg-decoder"
2775
2775
+
version = "0.1.22"
2776
2776
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2777
2777
+
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
2778
2778
+
2779
2779
+
[[package]]
1747
2780
name = "js-sys"
1748
2781
version = "0.3.91"
1749
2782
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1762
2795
"cfg-if",
1763
2796
"ecdsa",
1764
2797
"elliptic-curve",
2798
2798
+
"once_cell",
1765
2799
"sha2",
2800
2800
+
"signature",
1766
2801
]
1767
2802
1768
2803
[[package]]
···
1777
2812
]
1778
2813
1779
2814
[[package]]
2815
2815
+
name = "lazy-collections"
2816
2816
+
version = "0.11.0"
2817
2817
+
dependencies = [
2818
2818
+
"buffer",
2819
2819
+
"bytes",
2820
2820
+
"chrono",
2821
2821
+
"embedded-io 0.6.1",
2822
2822
+
"embedded-io-adapters",
2823
2823
+
"embedded-io-async",
2824
2824
+
"heapless 0.9.2",
2825
2825
+
"lock_api",
2826
2826
+
"loom",
2827
2827
+
"maitake-sync 0.2.2",
2828
2828
+
"managed",
2829
2829
+
"miette",
2830
2830
+
"n0-future",
2831
2831
+
"pin-project",
2832
2832
+
"postcard",
2833
2833
+
"proptest",
2834
2834
+
"serde",
2835
2835
+
"serde_bytes",
2836
2836
+
"tokio",
2837
2837
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2838
2838
+
"tracing 0.1.44 (git+https://github.com/tokio-rs/tracing)",
2839
2839
+
"tracing-subscriber 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
2840
2840
+
"tracing-subscriber 0.3.23 (git+https://github.com/tokio-rs/tracing)",
2841
2841
+
]
2842
2842
+
2843
2843
+
[[package]]
1780
2844
name = "lazy_static"
1781
2845
version = "1.5.0"
1782
2846
source = "registry+https://github.com/rust-lang/crates.io-index"
1783
2847
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
2848
2848
+
dependencies = [
2849
2849
+
"spin 0.9.8",
2850
2850
+
]
2851
2851
+
2852
2852
+
[[package]]
2853
2853
+
name = "lebe"
2854
2854
+
version = "0.5.3"
2855
2855
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2856
2856
+
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
1784
2857
1785
2858
[[package]]
1786
2859
name = "libc"
1787
2860
version = "0.2.183"
1788
2861
source = "registry+https://github.com/rust-lang/crates.io-index"
1789
2862
checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d"
2863
2863
+
2864
2864
+
[[package]]
2865
2865
+
name = "libfuzzer-sys"
2866
2866
+
version = "0.4.12"
2867
2867
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2868
2868
+
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
2869
2869
+
dependencies = [
2870
2870
+
"arbitrary",
2871
2871
+
"cc",
2872
2872
+
]
1790
2873
1791
2874
[[package]]
1792
2875
name = "libm"
···
1795
2878
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
1796
2879
1797
2880
[[package]]
2881
2881
+
name = "libredox"
2882
2882
+
version = "0.1.14"
2883
2883
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2884
2884
+
checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a"
2885
2885
+
dependencies = [
2886
2886
+
"bitflags",
2887
2887
+
"libc",
2888
2888
+
"plain",
2889
2889
+
"redox_syscall 0.7.3",
2890
2890
+
]
2891
2891
+
2892
2892
+
[[package]]
1798
2893
name = "linked-hash-map"
1799
2894
version = "0.5.6"
1800
2895
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1802
2897
1803
2898
[[package]]
1804
2899
name = "linux-raw-sys"
2900
2900
+
version = "0.4.15"
2901
2901
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2902
2902
+
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
2903
2903
+
2904
2904
+
[[package]]
2905
2905
+
name = "linux-raw-sys"
1805
2906
version = "0.12.1"
1806
2907
source = "registry+https://github.com/rust-lang/crates.io-index"
1807
2908
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
···
1836
2937
"cfg-if",
1837
2938
"generator",
1838
2939
"scoped-tls",
1839
1839
-
"tracing",
1840
1840
-
"tracing-subscriber",
2940
2940
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2941
2941
+
"tracing-subscriber 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
2942
2942
+
]
2943
2943
+
2944
2944
+
[[package]]
2945
2945
+
name = "loop9"
2946
2946
+
version = "0.1.5"
2947
2947
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2948
2948
+
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
2949
2949
+
dependencies = [
2950
2950
+
"imgref",
1841
2951
]
1842
2952
1843
2953
[[package]]
···
1856
2966
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
1857
2967
1858
2968
[[package]]
2969
2969
+
name = "mac"
2970
2970
+
version = "0.0.2"
2971
2971
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2972
2972
+
checksum = "1b1db08c0d0ddbb591e65f1da58d1cefccc94a2faa0c55bf979ce215a3e04d5e"
2973
2973
+
2974
2974
+
[[package]]
2975
2975
+
name = "mac"
2976
2976
+
version = "0.1.1"
2977
2977
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2978
2978
+
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
2979
2979
+
2980
2980
+
[[package]]
1859
2981
name = "maitake-sync"
1860
2982
version = "0.1.2"
1861
2983
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1869
2991
]
1870
2992
1871
2993
[[package]]
2994
2994
+
name = "maitake-sync"
2995
2995
+
version = "0.2.2"
2996
2996
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2997
2997
+
checksum = "748f86d9befd480b602c3bebc9ef30dbf2f3dfc8acc4a73d07b90f0117e6de3f"
2998
2998
+
dependencies = [
2999
2999
+
"cordyceps",
3000
3000
+
"loom",
3001
3001
+
"mutex-traits",
3002
3002
+
"mycelium-bitfield",
3003
3003
+
"pin-project",
3004
3004
+
"portable-atomic",
3005
3005
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
3006
3006
+
]
3007
3007
+
3008
3008
+
[[package]]
3009
3009
+
name = "managed"
3010
3010
+
version = "0.8.0"
3011
3011
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3012
3012
+
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
3013
3013
+
3014
3014
+
[[package]]
3015
3015
+
name = "markup5ever"
3016
3016
+
version = "0.12.1"
3017
3017
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3018
3018
+
checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45"
3019
3019
+
dependencies = [
3020
3020
+
"log",
3021
3021
+
"phf",
3022
3022
+
"phf_codegen",
3023
3023
+
"string_cache",
3024
3024
+
"string_cache_codegen",
3025
3025
+
"tendril",
3026
3026
+
]
3027
3027
+
3028
3028
+
[[package]]
3029
3029
+
name = "markup5ever_rcdom"
3030
3030
+
version = "0.3.0"
3031
3031
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3032
3032
+
checksum = "edaa21ab3701bfee5099ade5f7e1f84553fd19228cf332f13cd6e964bf59be18"
3033
3033
+
dependencies = [
3034
3034
+
"html5ever",
3035
3035
+
"markup5ever",
3036
3036
+
"tendril",
3037
3037
+
"xml5ever",
3038
3038
+
]
3039
3039
+
3040
3040
+
[[package]]
1872
3041
name = "match-lookup"
1873
3042
version = "0.1.2"
1874
3043
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1889
3058
]
1890
3059
1891
3060
[[package]]
3061
3061
+
name = "matchit"
3062
3062
+
version = "0.8.4"
3063
3063
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3064
3064
+
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
3065
3065
+
3066
3066
+
[[package]]
3067
3067
+
name = "maybe-rayon"
3068
3068
+
version = "0.1.1"
3069
3069
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3070
3070
+
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
3071
3071
+
dependencies = [
3072
3072
+
"cfg-if",
3073
3073
+
"rayon",
3074
3074
+
]
3075
3075
+
3076
3076
+
[[package]]
1892
3077
name = "memchr"
1893
3078
version = "2.8.0"
1894
3079
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1931
3116
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1932
3117
1933
3118
[[package]]
3119
3119
+
name = "mime_guess"
3120
3120
+
version = "2.0.5"
3121
3121
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3122
3122
+
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
3123
3123
+
dependencies = [
3124
3124
+
"mime",
3125
3125
+
"unicase",
3126
3126
+
]
3127
3127
+
3128
3128
+
[[package]]
1934
3129
name = "mini-moka-wasm"
1935
3130
version = "0.10.99"
1936
3131
dependencies = [
···
1960
3155
1961
3156
[[package]]
1962
3157
name = "miniz_oxide"
3158
3158
+
version = "0.4.4"
3159
3159
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3160
3160
+
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
3161
3161
+
dependencies = [
3162
3162
+
"adler",
3163
3163
+
"autocfg",
3164
3164
+
]
3165
3165
+
3166
3166
+
[[package]]
3167
3167
+
name = "miniz_oxide"
1963
3168
version = "0.8.9"
1964
3169
source = "registry+https://github.com/rust-lang/crates.io-index"
1965
3170
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
···
1980
3185
]
1981
3186
1982
3187
[[package]]
3188
3188
+
name = "moxcms"
3189
3189
+
version = "0.8.1"
3190
3190
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3191
3191
+
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
3192
3192
+
dependencies = [
3193
3193
+
"num-traits",
3194
3194
+
"pxfm",
3195
3195
+
]
3196
3196
+
3197
3197
+
[[package]]
1983
3198
name = "multibase"
1984
3199
version = "0.9.2"
1985
3200
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1999
3214
dependencies = [
2000
3215
"core2",
2001
3216
"serde",
2002
2002
-
"unsigned-varint",
3217
3217
+
"unsigned-varint 0.8.0",
3218
3218
+
]
3219
3219
+
3220
3220
+
[[package]]
3221
3221
+
name = "multipart"
3222
3222
+
version = "0.18.0"
3223
3223
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3224
3224
+
checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182"
3225
3225
+
dependencies = [
3226
3226
+
"buf_redux",
3227
3227
+
"httparse",
3228
3228
+
"log",
3229
3229
+
"mime",
3230
3230
+
"mime_guess",
3231
3231
+
"quick-error 1.2.3",
3232
3232
+
"rand 0.8.5",
3233
3233
+
"safemem",
3234
3234
+
"tempfile",
3235
3235
+
"twoway",
2003
3236
]
3237
3237
+
3238
3238
+
[[package]]
3239
3239
+
name = "mutex-traits"
3240
3240
+
version = "1.0.1"
3241
3241
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3242
3242
+
checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f"
2004
3243
2005
3244
[[package]]
2006
3245
name = "mycelium-bitfield"
···
2030
3269
]
2031
3270
2032
3271
[[package]]
3272
3272
+
name = "ndk-context"
3273
3273
+
version = "0.1.1"
3274
3274
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3275
3275
+
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
3276
3276
+
3277
3277
+
[[package]]
3278
3278
+
name = "new_debug_unreachable"
3279
3279
+
version = "1.0.6"
3280
3280
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3281
3281
+
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
3282
3282
+
3283
3283
+
[[package]]
3284
3284
+
name = "nom"
3285
3285
+
version = "8.0.0"
3286
3286
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3287
3287
+
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
3288
3288
+
dependencies = [
3289
3289
+
"memchr",
3290
3290
+
]
3291
3291
+
3292
3292
+
[[package]]
3293
3293
+
name = "noop_proc_macro"
3294
3294
+
version = "0.3.0"
3295
3295
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3296
3296
+
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
3297
3297
+
3298
3298
+
[[package]]
2033
3299
name = "nu-ansi-term"
2034
3300
version = "0.50.3"
2035
3301
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2063
3329
]
2064
3330
2065
3331
[[package]]
3332
3332
+
name = "num-bigint-dig"
3333
3333
+
version = "0.8.6"
3334
3334
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3335
3335
+
checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
3336
3336
+
dependencies = [
3337
3337
+
"lazy_static",
3338
3338
+
"libm",
3339
3339
+
"num-integer",
3340
3340
+
"num-iter",
3341
3341
+
"num-traits",
3342
3342
+
"rand 0.8.5",
3343
3343
+
"smallvec",
3344
3344
+
"zeroize",
3345
3345
+
]
3346
3346
+
3347
3347
+
[[package]]
2066
3348
name = "num-complex"
2067
3349
version = "0.4.6"
2068
3350
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2076
3358
version = "0.2.0"
2077
3359
source = "registry+https://github.com/rust-lang/crates.io-index"
2078
3360
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
3361
3361
+
3362
3362
+
[[package]]
3363
3363
+
name = "num-derive"
3364
3364
+
version = "0.4.2"
3365
3365
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3366
3366
+
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
3367
3367
+
dependencies = [
3368
3368
+
"proc-macro2",
3369
3369
+
"quote",
3370
3370
+
"syn",
3371
3371
+
]
2079
3372
2080
3373
[[package]]
2081
3374
name = "num-integer"
···
2119
3412
]
2120
3413
2121
3414
[[package]]
3415
3415
+
name = "num_cpus"
3416
3416
+
version = "1.17.0"
3417
3417
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3418
3418
+
checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b"
3419
3419
+
dependencies = [
3420
3420
+
"hermit-abi",
3421
3421
+
"libc",
3422
3422
+
]
3423
3423
+
3424
3424
+
[[package]]
3425
3425
+
name = "num_threads"
3426
3426
+
version = "0.1.7"
3427
3427
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3428
3428
+
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
3429
3429
+
dependencies = [
3430
3430
+
"libc",
3431
3431
+
]
3432
3432
+
3433
3433
+
[[package]]
3434
3434
+
name = "objc2"
3435
3435
+
version = "0.6.4"
3436
3436
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3437
3437
+
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
3438
3438
+
dependencies = [
3439
3439
+
"objc2-encode",
3440
3440
+
]
3441
3441
+
3442
3442
+
[[package]]
3443
3443
+
name = "objc2-encode"
3444
3444
+
version = "4.1.0"
3445
3445
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3446
3446
+
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
3447
3447
+
3448
3448
+
[[package]]
3449
3449
+
name = "objc2-foundation"
3450
3450
+
version = "0.3.2"
3451
3451
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3452
3452
+
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
3453
3453
+
dependencies = [
3454
3454
+
"bitflags",
3455
3455
+
"objc2",
3456
3456
+
]
3457
3457
+
3458
3458
+
[[package]]
2122
3459
name = "object"
2123
3460
version = "0.37.3"
2124
3461
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2203
3540
]
2204
3541
2205
3542
[[package]]
3543
3543
+
name = "p384"
3544
3544
+
version = "0.13.1"
3545
3545
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3546
3546
+
checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6"
3547
3547
+
dependencies = [
3548
3548
+
"ecdsa",
3549
3549
+
"elliptic-curve",
3550
3550
+
"primeorder",
3551
3551
+
"sha2",
3552
3552
+
]
3553
3553
+
3554
3554
+
[[package]]
2206
3555
name = "parking"
2207
3556
version = "2.2.1"
2208
3557
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2226
3575
dependencies = [
2227
3576
"cfg-if",
2228
3577
"libc",
2229
2229
-
"redox_syscall",
3578
3578
+
"redox_syscall 0.5.18",
2230
3579
"smallvec",
2231
3580
"windows-link",
2232
3581
]
3582
3582
+
3583
3583
+
[[package]]
3584
3584
+
name = "paste"
3585
3585
+
version = "1.0.15"
3586
3586
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3587
3587
+
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
3588
3588
+
3589
3589
+
[[package]]
3590
3590
+
name = "pastey"
3591
3591
+
version = "0.1.1"
3592
3592
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3593
3593
+
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
2233
3594
2234
3595
[[package]]
2235
3596
name = "pem-rfc7468"
···
2257
3618
]
2258
3619
2259
3620
[[package]]
3621
3621
+
name = "phf_codegen"
3622
3622
+
version = "0.11.3"
3623
3623
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3624
3624
+
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
3625
3625
+
dependencies = [
3626
3626
+
"phf_generator",
3627
3627
+
"phf_shared",
3628
3628
+
]
3629
3629
+
3630
3630
+
[[package]]
2260
3631
name = "phf_generator"
2261
3632
version = "0.11.3"
2262
3633
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2321
3692
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
2322
3693
2323
3694
[[package]]
3695
3695
+
name = "pkcs1"
3696
3696
+
version = "0.7.5"
3697
3697
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3698
3698
+
checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
3699
3699
+
dependencies = [
3700
3700
+
"der",
3701
3701
+
"pkcs8",
3702
3702
+
"spki",
3703
3703
+
]
3704
3704
+
3705
3705
+
[[package]]
2324
3706
name = "pkcs8"
2325
3707
version = "0.10.2"
2326
3708
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2337
3719
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
2338
3720
2339
3721
[[package]]
3722
3722
+
name = "plain"
3723
3723
+
version = "0.2.3"
3724
3724
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3725
3725
+
checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6"
3726
3726
+
3727
3727
+
[[package]]
3728
3728
+
name = "png"
3729
3729
+
version = "0.18.1"
3730
3730
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3731
3731
+
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
3732
3732
+
dependencies = [
3733
3733
+
"bitflags",
3734
3734
+
"crc32fast",
3735
3735
+
"fdeflate",
3736
3736
+
"flate2",
3737
3737
+
"miniz_oxide 0.8.9",
3738
3738
+
]
3739
3739
+
3740
3740
+
[[package]]
2340
3741
name = "portable-atomic"
2341
3742
version = "1.13.1"
2342
3743
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2351
3752
"cobs",
2352
3753
"embedded-io 0.4.0",
2353
3754
"embedded-io 0.6.1",
2354
2354
-
"heapless",
3755
3755
+
"heapless 0.7.17",
2355
3756
"serde",
2356
3757
]
2357
3758
···
2380
3781
]
2381
3782
2382
3783
[[package]]
3784
3784
+
name = "precomputed-hash"
3785
3785
+
version = "0.1.1"
3786
3786
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3787
3787
+
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
3788
3788
+
3789
3789
+
[[package]]
3790
3790
+
name = "pretty_assertions"
3791
3791
+
version = "1.4.1"
3792
3792
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3793
3793
+
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
3794
3794
+
dependencies = [
3795
3795
+
"diff",
3796
3796
+
"yansi",
3797
3797
+
]
3798
3798
+
3799
3799
+
[[package]]
2383
3800
name = "prettyplease"
2384
3801
version = "0.2.37"
2385
3802
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2421
3838
]
2422
3839
2423
3840
[[package]]
3841
3841
+
name = "profiling"
3842
3842
+
version = "1.0.17"
3843
3843
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3844
3844
+
checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773"
3845
3845
+
dependencies = [
3846
3846
+
"profiling-procmacros",
3847
3847
+
]
3848
3848
+
3849
3849
+
[[package]]
3850
3850
+
name = "profiling-procmacros"
3851
3851
+
version = "1.0.17"
3852
3852
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3853
3853
+
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
3854
3854
+
dependencies = [
3855
3855
+
"quote",
3856
3856
+
"syn",
3857
3857
+
]
3858
3858
+
3859
3859
+
[[package]]
3860
3860
+
name = "proptest"
3861
3861
+
version = "1.10.0"
3862
3862
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3863
3863
+
checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532"
3864
3864
+
dependencies = [
3865
3865
+
"bit-set",
3866
3866
+
"bit-vec",
3867
3867
+
"bitflags",
3868
3868
+
"num-traits",
3869
3869
+
"rand 0.9.2",
3870
3870
+
"rand_chacha 0.9.0",
3871
3871
+
"rand_xorshift",
3872
3872
+
"regex-syntax",
3873
3873
+
"rusty-fork",
3874
3874
+
"tempfile",
3875
3875
+
"unarray",
3876
3876
+
]
3877
3877
+
3878
3878
+
[[package]]
3879
3879
+
name = "pxfm"
3880
3880
+
version = "0.1.28"
3881
3881
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3882
3882
+
checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
3883
3883
+
3884
3884
+
[[package]]
3885
3885
+
name = "qoi"
3886
3886
+
version = "0.4.1"
3887
3887
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3888
3888
+
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
3889
3889
+
dependencies = [
3890
3890
+
"bytemuck",
3891
3891
+
]
3892
3892
+
3893
3893
+
[[package]]
3894
3894
+
name = "quick-error"
3895
3895
+
version = "1.2.3"
3896
3896
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3897
3897
+
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
3898
3898
+
3899
3899
+
[[package]]
3900
3900
+
name = "quick-error"
3901
3901
+
version = "2.0.1"
3902
3902
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3903
3903
+
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
3904
3904
+
3905
3905
+
[[package]]
2424
3906
name = "quinn"
2425
3907
version = "0.11.9"
2426
3908
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2436
3918
"socket2 0.6.3",
2437
3919
"thiserror 2.0.18",
2438
3920
"tokio",
2439
2439
-
"tracing",
3921
3921
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2440
3922
"web-time",
2441
3923
]
2442
3924
···
2457
3939
"slab",
2458
3940
"thiserror 2.0.18",
2459
3941
"tinyvec",
2460
2460
-
"tracing",
3942
3942
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2461
3943
"web-time",
2462
3944
]
2463
3945
···
2471
3953
"libc",
2472
3954
"once_cell",
2473
3955
"socket2 0.6.3",
2474
2474
-
"tracing",
3956
3956
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
2475
3957
"windows-sys 0.60.2",
2476
3958
]
2477
3959
···
2550
4032
]
2551
4033
2552
4034
[[package]]
4035
4035
+
name = "rand_xorshift"
4036
4036
+
version = "0.4.0"
4037
4037
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4038
4038
+
checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a"
4039
4039
+
dependencies = [
4040
4040
+
"rand_core 0.9.5",
4041
4041
+
]
4042
4042
+
4043
4043
+
[[package]]
4044
4044
+
name = "rav1e"
4045
4045
+
version = "0.8.1"
4046
4046
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4047
4047
+
checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
4048
4048
+
dependencies = [
4049
4049
+
"aligned-vec",
4050
4050
+
"arbitrary",
4051
4051
+
"arg_enum_proc_macro",
4052
4052
+
"arrayvec 0.7.6",
4053
4053
+
"av-scenechange",
4054
4054
+
"av1-grain",
4055
4055
+
"bitstream-io",
4056
4056
+
"built",
4057
4057
+
"cfg-if",
4058
4058
+
"interpolate_name",
4059
4059
+
"itertools",
4060
4060
+
"libc",
4061
4061
+
"libfuzzer-sys",
4062
4062
+
"log",
4063
4063
+
"maybe-rayon",
4064
4064
+
"new_debug_unreachable",
4065
4065
+
"noop_proc_macro",
4066
4066
+
"num-derive",
4067
4067
+
"num-traits",
4068
4068
+
"paste",
4069
4069
+
"profiling",
4070
4070
+
"rand 0.9.2",
4071
4071
+
"rand_chacha 0.9.0",
4072
4072
+
"simd_helpers",
4073
4073
+
"thiserror 2.0.18",
4074
4074
+
"v_frame",
4075
4075
+
"wasm-bindgen",
4076
4076
+
]
4077
4077
+
4078
4078
+
[[package]]
4079
4079
+
name = "ravif"
4080
4080
+
version = "0.13.0"
4081
4081
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4082
4082
+
checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
4083
4083
+
dependencies = [
4084
4084
+
"avif-serialize",
4085
4085
+
"imgref",
4086
4086
+
"loop9",
4087
4087
+
"quick-error 2.0.1",
4088
4088
+
"rav1e",
4089
4089
+
"rayon",
4090
4090
+
"rgb",
4091
4091
+
]
4092
4092
+
4093
4093
+
[[package]]
4094
4094
+
name = "rayon"
4095
4095
+
version = "1.11.0"
4096
4096
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4097
4097
+
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
4098
4098
+
dependencies = [
4099
4099
+
"either",
4100
4100
+
"rayon-core",
4101
4101
+
]
4102
4102
+
4103
4103
+
[[package]]
4104
4104
+
name = "rayon-core"
4105
4105
+
version = "1.13.0"
4106
4106
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4107
4107
+
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
4108
4108
+
dependencies = [
4109
4109
+
"crossbeam-deque",
4110
4110
+
"crossbeam-utils",
4111
4111
+
]
4112
4112
+
4113
4113
+
[[package]]
2553
4114
name = "redox_syscall"
2554
4115
version = "0.5.18"
2555
4116
source = "registry+https://github.com/rust-lang/crates.io-index"
2556
4117
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
4118
4118
+
dependencies = [
4119
4119
+
"bitflags",
4120
4120
+
]
4121
4121
+
4122
4122
+
[[package]]
4123
4123
+
name = "redox_syscall"
4124
4124
+
version = "0.7.3"
4125
4125
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4126
4126
+
checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16"
2557
4127
dependencies = [
2558
4128
"bitflags",
2559
4129
]
···
2619
4189
source = "registry+https://github.com/rust-lang/crates.io-index"
2620
4190
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
2621
4191
dependencies = [
2622
2622
-
"base64",
4192
4192
+
"base64 0.22.1",
2623
4193
"bytes",
2624
4194
"encoding_rs",
2625
4195
"futures-core",
···
2658
4228
]
2659
4229
2660
4230
[[package]]
4231
4231
+
name = "reserve-port"
4232
4232
+
version = "2.4.0"
4233
4233
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4234
4234
+
checksum = "94070964579245eb2f76e62a7668fe87bd9969ed6c41256f3bf614e3323dd3cc"
4235
4235
+
dependencies = [
4236
4236
+
"thiserror 2.0.18",
4237
4237
+
]
4238
4238
+
4239
4239
+
[[package]]
2661
4240
name = "resolv-conf"
2662
4241
version = "0.7.6"
2663
4242
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2674
4253
]
2675
4254
2676
4255
[[package]]
4256
4256
+
name = "rgb"
4257
4257
+
version = "0.8.53"
4258
4258
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4259
4259
+
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
4260
4260
+
dependencies = [
4261
4261
+
"bytemuck",
4262
4262
+
]
4263
4263
+
4264
4264
+
[[package]]
2677
4265
name = "ring"
2678
4266
version = "0.17.14"
2679
4267
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2694
4282
checksum = "dbf2048e0e979efb2ca7b91c4f1a8d77c91853e9b987c94c555668a8994915ad"
2695
4283
2696
4284
[[package]]
4285
4285
+
name = "rouille"
4286
4286
+
version = "3.6.2"
4287
4287
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4288
4288
+
checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921"
4289
4289
+
dependencies = [
4290
4290
+
"base64 0.13.1",
4291
4291
+
"brotli",
4292
4292
+
"chrono",
4293
4293
+
"deflate",
4294
4294
+
"filetime",
4295
4295
+
"multipart",
4296
4296
+
"percent-encoding",
4297
4297
+
"rand 0.8.5",
4298
4298
+
"serde",
4299
4299
+
"serde_derive",
4300
4300
+
"serde_json",
4301
4301
+
"sha1_smol",
4302
4302
+
"threadpool",
4303
4303
+
"time",
4304
4304
+
"tiny_http",
4305
4305
+
"url",
4306
4306
+
]
4307
4307
+
4308
4308
+
[[package]]
4309
4309
+
name = "rsa"
4310
4310
+
version = "0.9.10"
4311
4311
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4312
4312
+
checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
4313
4313
+
dependencies = [
4314
4314
+
"const-oid",
4315
4315
+
"digest",
4316
4316
+
"num-bigint-dig",
4317
4317
+
"num-integer",
4318
4318
+
"num-traits",
4319
4319
+
"pkcs1",
4320
4320
+
"pkcs8",
4321
4321
+
"rand_core 0.6.4",
4322
4322
+
"signature",
4323
4323
+
"spki",
4324
4324
+
"subtle",
4325
4325
+
"zeroize",
4326
4326
+
]
4327
4327
+
4328
4328
+
[[package]]
4329
4329
+
name = "rust-multipart-rfc7578_2"
4330
4330
+
version = "0.8.0"
4331
4331
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4332
4332
+
checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41"
4333
4333
+
dependencies = [
4334
4334
+
"bytes",
4335
4335
+
"futures-core",
4336
4336
+
"futures-util",
4337
4337
+
"http",
4338
4338
+
"mime",
4339
4339
+
"rand 0.9.2",
4340
4340
+
"thiserror 2.0.18",
4341
4341
+
]
4342
4342
+
4343
4343
+
[[package]]
2697
4344
name = "rustc-demangle"
2698
4345
version = "0.1.27"
2699
4346
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2716
4363
2717
4364
[[package]]
2718
4365
name = "rustix"
4366
4366
+
version = "0.38.44"
4367
4367
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4368
4368
+
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
4369
4369
+
dependencies = [
4370
4370
+
"bitflags",
4371
4371
+
"errno",
4372
4372
+
"libc",
4373
4373
+
"linux-raw-sys 0.4.15",
4374
4374
+
"windows-sys 0.52.0",
4375
4375
+
]
4376
4376
+
4377
4377
+
[[package]]
4378
4378
+
name = "rustix"
2719
4379
version = "1.1.4"
2720
4380
source = "registry+https://github.com/rust-lang/crates.io-index"
2721
4381
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
···
2723
4383
"bitflags",
2724
4384
"errno",
2725
4385
"libc",
2726
2726
-
"linux-raw-sys",
4386
4386
+
"linux-raw-sys 0.12.1",
2727
4387
"windows-sys 0.61.2",
2728
4388
]
2729
4389
···
2781
4441
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
2782
4442
2783
4443
[[package]]
4444
4444
+
name = "rusty-fork"
4445
4445
+
version = "0.3.1"
4446
4446
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4447
4447
+
checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2"
4448
4448
+
dependencies = [
4449
4449
+
"fnv",
4450
4450
+
"quick-error 1.2.3",
4451
4451
+
"tempfile",
4452
4452
+
"wait-timeout",
4453
4453
+
]
4454
4454
+
4455
4455
+
[[package]]
2784
4456
name = "ryu"
2785
4457
version = "1.0.23"
2786
4458
source = "registry+https://github.com/rust-lang/crates.io-index"
2787
4459
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
4460
4460
+
4461
4461
+
[[package]]
4462
4462
+
name = "safemem"
4463
4463
+
version = "0.3.3"
4464
4464
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4465
4465
+
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
2788
4466
2789
4467
[[package]]
2790
4468
name = "same-file"
···
2930
4608
]
2931
4609
2932
4610
[[package]]
4611
4611
+
name = "serde_ipld_dagjson"
4612
4612
+
version = "0.2.1"
4613
4613
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4614
4614
+
checksum = "82d2d9d1f29999ee9a3d774fe2a5db4cc199da5178d0350f5e4482ea04252aee"
4615
4615
+
dependencies = [
4616
4616
+
"ipld-core",
4617
4617
+
"serde",
4618
4618
+
"serde_json",
4619
4619
+
]
4620
4620
+
4621
4621
+
[[package]]
2933
4622
name = "serde_json"
2934
4623
version = "1.0.149"
2935
4624
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3001
4690
source = "registry+https://github.com/rust-lang/crates.io-index"
3002
4691
checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
3003
4692
dependencies = [
3004
3004
-
"base64",
4693
4693
+
"base64 0.22.1",
3005
4694
"chrono",
3006
4695
"hex",
3007
4696
"serde_core",
···
3034
4723
]
3035
4724
3036
4725
[[package]]
4726
4726
+
name = "sha1_smol"
4727
4727
+
version = "1.0.1"
4728
4728
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4729
4729
+
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
4730
4730
+
4731
4731
+
[[package]]
3037
4732
name = "sha2"
3038
4733
version = "0.10.9"
3039
4734
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3086
4781
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
3087
4782
3088
4783
[[package]]
4784
4784
+
name = "simd_cesu8"
4785
4785
+
version = "1.1.1"
4786
4786
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4787
4787
+
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
4788
4788
+
dependencies = [
4789
4789
+
"rustc_version",
4790
4790
+
"simdutf8",
4791
4791
+
]
4792
4792
+
4793
4793
+
[[package]]
4794
4794
+
name = "simd_helpers"
4795
4795
+
version = "0.1.0"
4796
4796
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4797
4797
+
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
4798
4798
+
dependencies = [
4799
4799
+
"quote",
4800
4800
+
]
4801
4801
+
4802
4802
+
[[package]]
4803
4803
+
name = "simdutf8"
4804
4804
+
version = "0.1.5"
4805
4805
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4806
4806
+
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
4807
4807
+
4808
4808
+
[[package]]
3089
4809
name = "siphasher"
3090
4810
version = "1.0.2"
3091
4811
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3171
4891
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
3172
4892
3173
4893
[[package]]
4894
4894
+
name = "string_cache"
4895
4895
+
version = "0.8.9"
4896
4896
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4897
4897
+
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
4898
4898
+
dependencies = [
4899
4899
+
"new_debug_unreachable",
4900
4900
+
"parking_lot",
4901
4901
+
"phf_shared",
4902
4902
+
"precomputed-hash",
4903
4903
+
"serde",
4904
4904
+
]
4905
4905
+
4906
4906
+
[[package]]
4907
4907
+
name = "string_cache_codegen"
4908
4908
+
version = "0.5.4"
4909
4909
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4910
4910
+
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
4911
4911
+
dependencies = [
4912
4912
+
"phf_generator",
4913
4913
+
"phf_shared",
4914
4914
+
"proc-macro2",
4915
4915
+
"quote",
4916
4916
+
]
4917
4917
+
4918
4918
+
[[package]]
3174
4919
name = "strsim"
3175
4920
version = "0.11.1"
3176
4921
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3276
5021
"fastrand",
3277
5022
"getrandom 0.3.4",
3278
5023
"once_cell",
3279
3279
-
"rustix",
5024
5024
+
"rustix 1.1.4",
3280
5025
"windows-sys 0.61.2",
3281
5026
]
3282
5027
3283
5028
[[package]]
5029
5029
+
name = "tendril"
5030
5030
+
version = "0.4.3"
5031
5031
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5032
5032
+
checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0"
5033
5033
+
dependencies = [
5034
5034
+
"futf",
5035
5035
+
"mac 0.1.1",
5036
5036
+
"utf-8",
5037
5037
+
]
5038
5038
+
5039
5039
+
[[package]]
3284
5040
name = "termcolor"
3285
5041
version = "1.4.1"
3286
5042
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3295
5051
source = "registry+https://github.com/rust-lang/crates.io-index"
3296
5052
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
3297
5053
dependencies = [
3298
3298
-
"rustix",
5054
5054
+
"rustix 1.1.4",
3299
5055
"windows-sys 0.60.2",
3300
5056
]
3301
5057
···
3359
5115
]
3360
5116
3361
5117
[[package]]
5118
5118
+
name = "threadpool"
5119
5119
+
version = "1.8.1"
5120
5120
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5121
5121
+
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
5122
5122
+
dependencies = [
5123
5123
+
"num_cpus",
5124
5124
+
]
5125
5125
+
5126
5126
+
[[package]]
5127
5127
+
name = "tiff"
5128
5128
+
version = "0.6.1"
5129
5129
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5130
5130
+
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
5131
5131
+
dependencies = [
5132
5132
+
"jpeg-decoder",
5133
5133
+
"miniz_oxide 0.4.4",
5134
5134
+
"weezl",
5135
5135
+
]
5136
5136
+
5137
5137
+
[[package]]
5138
5138
+
name = "tiff"
5139
5139
+
version = "0.11.3"
5140
5140
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5141
5141
+
checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
5142
5142
+
dependencies = [
5143
5143
+
"fax",
5144
5144
+
"flate2",
5145
5145
+
"half",
5146
5146
+
"quick-error 2.0.1",
5147
5147
+
"weezl",
5148
5148
+
"zune-jpeg",
5149
5149
+
]
5150
5150
+
5151
5151
+
[[package]]
3362
5152
name = "time"
3363
5153
version = "0.3.47"
3364
5154
source = "registry+https://github.com/rust-lang/crates.io-index"
3365
5155
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
3366
5156
dependencies = [
3367
5157
"deranged",
5158
5158
+
"itoa",
5159
5159
+
"libc",
3368
5160
"num-conv",
5161
5161
+
"num_threads",
3369
5162
"powerfmt",
3370
5163
"serde_core",
3371
5164
"time-core",
5165
5165
+
"time-macros",
3372
5166
]
3373
5167
3374
5168
[[package]]
···
3376
5170
version = "0.1.8"
3377
5171
source = "registry+https://github.com/rust-lang/crates.io-index"
3378
5172
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
5173
5173
+
5174
5174
+
[[package]]
5175
5175
+
name = "time-macros"
5176
5176
+
version = "0.2.27"
5177
5177
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5178
5178
+
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
5179
5179
+
dependencies = [
5180
5180
+
"num-conv",
5181
5181
+
"time-core",
5182
5182
+
]
5183
5183
+
5184
5184
+
[[package]]
5185
5185
+
name = "tiny_http"
5186
5186
+
version = "0.12.0"
5187
5187
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5188
5188
+
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
5189
5189
+
dependencies = [
5190
5190
+
"ascii",
5191
5191
+
"chunked_transfer",
5192
5192
+
"httpdate",
5193
5193
+
"log",
5194
5194
+
]
3379
5195
3380
5196
[[package]]
3381
5197
name = "tinystr"
···
3582
5398
"tokio",
3583
5399
"tower-layer",
3584
5400
"tower-service",
5401
5401
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
3585
5402
]
3586
5403
3587
5404
[[package]]
···
3605
5422
"tower",
3606
5423
"tower-layer",
3607
5424
"tower-service",
5425
5425
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
3608
5426
]
3609
5427
3610
5428
[[package]]
···
3625
5443
source = "registry+https://github.com/rust-lang/crates.io-index"
3626
5444
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
3627
5445
dependencies = [
5446
5446
+
"log",
3628
5447
"pin-project-lite",
3629
3629
-
"tracing-attributes",
3630
3630
-
"tracing-core",
5448
5448
+
"tracing-attributes 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)",
5449
5449
+
"tracing-core 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
5450
5450
+
]
5451
5451
+
5452
5452
+
[[package]]
5453
5453
+
name = "tracing"
5454
5454
+
version = "0.1.44"
5455
5455
+
source = "git+https://github.com/tokio-rs/tracing#54ede4d5d85a536aed5485c5213011d9ec961935"
5456
5456
+
dependencies = [
5457
5457
+
"pin-project-lite",
5458
5458
+
"tracing-attributes 0.1.31 (git+https://github.com/tokio-rs/tracing)",
5459
5459
+
"tracing-core 0.1.36 (git+https://github.com/tokio-rs/tracing)",
3631
5460
]
3632
5461
3633
5462
[[package]]
···
3642
5471
]
3643
5472
3644
5473
[[package]]
5474
5474
+
name = "tracing-attributes"
5475
5475
+
version = "0.1.31"
5476
5476
+
source = "git+https://github.com/tokio-rs/tracing#54ede4d5d85a536aed5485c5213011d9ec961935"
5477
5477
+
dependencies = [
5478
5478
+
"proc-macro2",
5479
5479
+
"quote",
5480
5480
+
"syn",
5481
5481
+
]
5482
5482
+
5483
5483
+
[[package]]
3645
5484
name = "tracing-core"
3646
5485
version = "0.1.36"
3647
5486
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3652
5491
]
3653
5492
3654
5493
[[package]]
5494
5494
+
name = "tracing-core"
5495
5495
+
version = "0.1.36"
5496
5496
+
source = "git+https://github.com/tokio-rs/tracing#54ede4d5d85a536aed5485c5213011d9ec961935"
5497
5497
+
dependencies = [
5498
5498
+
"valuable",
5499
5499
+
]
5500
5500
+
5501
5501
+
[[package]]
3655
5502
name = "tracing-log"
3656
5503
version = "0.2.0"
3657
5504
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3659
5506
dependencies = [
3660
5507
"log",
3661
5508
"once_cell",
3662
3662
-
"tracing-core",
5509
5509
+
"tracing-core 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
5510
5510
+
]
5511
5511
+
5512
5512
+
[[package]]
5513
5513
+
name = "tracing-log"
5514
5514
+
version = "0.2.0"
5515
5515
+
source = "git+https://github.com/tokio-rs/tracing#54ede4d5d85a536aed5485c5213011d9ec961935"
5516
5516
+
dependencies = [
5517
5517
+
"log",
5518
5518
+
"once_cell",
5519
5519
+
"tracing-core 0.1.36 (git+https://github.com/tokio-rs/tracing)",
3663
5520
]
3664
5521
3665
5522
[[package]]
···
3675
5532
"sharded-slab",
3676
5533
"smallvec",
3677
5534
"thread_local",
3678
3678
-
"tracing",
3679
3679
-
"tracing-core",
3680
3680
-
"tracing-log",
5535
5535
+
"time",
5536
5536
+
"tracing 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)",
5537
5537
+
"tracing-core 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
5538
5538
+
"tracing-log 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
5539
5539
+
]
5540
5540
+
5541
5541
+
[[package]]
5542
5542
+
name = "tracing-subscriber"
5543
5543
+
version = "0.3.23"
5544
5544
+
source = "git+https://github.com/tokio-rs/tracing#54ede4d5d85a536aed5485c5213011d9ec961935"
5545
5545
+
dependencies = [
5546
5546
+
"matchers",
5547
5547
+
"nu-ansi-term",
5548
5548
+
"once_cell",
5549
5549
+
"regex-automata",
5550
5550
+
"sharded-slab",
5551
5551
+
"smallvec",
5552
5552
+
"thread_local",
5553
5553
+
"tracing 0.1.44 (git+https://github.com/tokio-rs/tracing)",
5554
5554
+
"tracing-core 0.1.36 (git+https://github.com/tokio-rs/tracing)",
5555
5555
+
"tracing-log 0.2.0 (git+https://github.com/tokio-rs/tracing)",
3681
5556
]
3682
5557
3683
5558
[[package]]
···
3739
5614
]
3740
5615
3741
5616
[[package]]
5617
5617
+
name = "twoway"
5618
5618
+
version = "0.1.8"
5619
5619
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5620
5620
+
checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
5621
5621
+
dependencies = [
5622
5622
+
"memchr",
5623
5623
+
]
5624
5624
+
5625
5625
+
[[package]]
5626
5626
+
name = "typeid"
5627
5627
+
version = "1.0.3"
5628
5628
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5629
5629
+
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
5630
5630
+
5631
5631
+
[[package]]
3742
5632
name = "typenum"
3743
5633
version = "1.19.0"
3744
5634
source = "registry+https://github.com/rust-lang/crates.io-index"
3745
5635
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
3746
5636
3747
5637
[[package]]
5638
5638
+
name = "typetag"
5639
5639
+
version = "0.2.21"
5640
5640
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5641
5641
+
checksum = "be2212c8a9b9bcfca32024de14998494cf9a5dfa59ea1b829de98bac374b86bf"
5642
5642
+
dependencies = [
5643
5643
+
"erased-serde",
5644
5644
+
"inventory",
5645
5645
+
"once_cell",
5646
5646
+
"serde",
5647
5647
+
"typetag-impl",
5648
5648
+
]
5649
5649
+
5650
5650
+
[[package]]
5651
5651
+
name = "typetag-impl"
5652
5652
+
version = "0.2.21"
5653
5653
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5654
5654
+
checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846"
5655
5655
+
dependencies = [
5656
5656
+
"proc-macro2",
5657
5657
+
"quote",
5658
5658
+
"syn",
5659
5659
+
]
5660
5660
+
5661
5661
+
[[package]]
5662
5662
+
name = "unarray"
5663
5663
+
version = "0.1.4"
5664
5664
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5665
5665
+
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
5666
5666
+
5667
5667
+
[[package]]
5668
5668
+
name = "unicase"
5669
5669
+
version = "2.9.0"
5670
5670
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5671
5671
+
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
5672
5672
+
5673
5673
+
[[package]]
3748
5674
name = "unicode-ident"
3749
5675
version = "1.0.24"
3750
5676
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3782
5708
3783
5709
[[package]]
3784
5710
name = "unsigned-varint"
5711
5711
+
version = "0.7.2"
5712
5712
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5713
5713
+
checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105"
5714
5714
+
5715
5715
+
[[package]]
5716
5716
+
name = "unsigned-varint"
3785
5717
version = "0.8.0"
3786
5718
source = "registry+https://github.com/rust-lang/crates.io-index"
3787
5719
checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06"
···
3821
5753
version = "0.2.2"
3822
5754
source = "registry+https://github.com/rust-lang/crates.io-index"
3823
5755
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
5756
5756
+
5757
5757
+
[[package]]
5758
5758
+
name = "uuid"
5759
5759
+
version = "1.22.0"
5760
5760
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5761
5761
+
checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37"
5762
5762
+
dependencies = [
5763
5763
+
"js-sys",
5764
5764
+
"wasm-bindgen",
5765
5765
+
]
5766
5766
+
5767
5767
+
[[package]]
5768
5768
+
name = "v_frame"
5769
5769
+
version = "0.3.9"
5770
5770
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5771
5771
+
checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2"
5772
5772
+
dependencies = [
5773
5773
+
"aligned-vec",
5774
5774
+
"num-traits",
5775
5775
+
"wasm-bindgen",
5776
5776
+
]
3824
5777
3825
5778
[[package]]
3826
5779
name = "valuable"
···
3835
5788
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
3836
5789
3837
5790
[[package]]
5791
5791
+
name = "viuer"
5792
5792
+
version = "0.9.2"
5793
5793
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5794
5794
+
checksum = "0ae7c6870b98c838123f22cac9a594cbe2d74ea48d79271c08f8c9e680b40fac"
5795
5795
+
dependencies = [
5796
5796
+
"ansi_colours",
5797
5797
+
"base64 0.22.1",
5798
5798
+
"console",
5799
5799
+
"crossterm",
5800
5800
+
"image",
5801
5801
+
"lazy_static",
5802
5802
+
"tempfile",
5803
5803
+
"termcolor",
5804
5804
+
]
5805
5805
+
5806
5806
+
[[package]]
5807
5807
+
name = "wait-timeout"
5808
5808
+
version = "0.2.1"
5809
5809
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5810
5810
+
checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11"
5811
5811
+
dependencies = [
5812
5812
+
"libc",
5813
5813
+
]
5814
5814
+
5815
5815
+
[[package]]
3838
5816
name = "walkdir"
3839
5817
version = "2.5.0"
3840
5818
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4000
5978
]
4001
5979
4002
5980
[[package]]
5981
5981
+
name = "webbrowser"
5982
5982
+
version = "1.2.0"
5983
5983
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5984
5984
+
checksum = "fe985f41e291eecef5e5c0770a18d28390addb03331c043964d9e916453d6f16"
5985
5985
+
dependencies = [
5986
5986
+
"core-foundation 0.10.1",
5987
5987
+
"jni",
5988
5988
+
"log",
5989
5989
+
"ndk-context",
5990
5990
+
"objc2",
5991
5991
+
"objc2-foundation",
5992
5992
+
"url",
5993
5993
+
"web-sys",
5994
5994
+
]
5995
5995
+
5996
5996
+
[[package]]
5997
5997
+
name = "webpage"
5998
5998
+
version = "2.0.1"
5999
5999
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6000
6000
+
checksum = "70862efc041d46e6bbaa82bb9c34ae0596d090e86cbd14bd9e93b36ee6802eac"
6001
6001
+
dependencies = [
6002
6002
+
"html5ever",
6003
6003
+
"markup5ever_rcdom",
6004
6004
+
"serde_json",
6005
6005
+
"url",
6006
6006
+
]
6007
6007
+
6008
6008
+
[[package]]
4003
6009
name = "webpki-roots"
4004
6010
version = "1.0.6"
4005
6011
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4009
6015
]
4010
6016
4011
6017
[[package]]
6018
6018
+
name = "weezl"
6019
6019
+
version = "0.1.12"
6020
6020
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6021
6021
+
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
6022
6022
+
6023
6023
+
[[package]]
4012
6024
name = "widestring"
4013
6025
version = "1.2.1"
4014
6026
source = "registry+https://github.com/rust-lang/crates.io-index"
4015
6027
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
4016
6028
4017
6029
[[package]]
6030
6030
+
name = "winapi"
6031
6031
+
version = "0.3.9"
6032
6032
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6033
6033
+
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
6034
6034
+
dependencies = [
6035
6035
+
"winapi-i686-pc-windows-gnu",
6036
6036
+
"winapi-x86_64-pc-windows-gnu",
6037
6037
+
]
6038
6038
+
6039
6039
+
[[package]]
6040
6040
+
name = "winapi-i686-pc-windows-gnu"
6041
6041
+
version = "0.4.0"
6042
6042
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6043
6043
+
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
6044
6044
+
6045
6045
+
[[package]]
4018
6046
name = "winapi-util"
4019
6047
version = "0.1.11"
4020
6048
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4022
6050
dependencies = [
4023
6051
"windows-sys 0.61.2",
4024
6052
]
6053
6053
+
6054
6054
+
[[package]]
6055
6055
+
name = "winapi-x86_64-pc-windows-gnu"
6056
6056
+
version = "0.4.0"
6057
6057
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6058
6058
+
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
4025
6059
4026
6060
[[package]]
4027
6061
name = "windows-core"
···
4107
6141
version = "0.52.0"
4108
6142
source = "registry+https://github.com/rust-lang/crates.io-index"
4109
6143
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
6144
6144
+
dependencies = [
6145
6145
+
"windows-targets 0.52.6",
6146
6146
+
]
6147
6147
+
6148
6148
+
[[package]]
6149
6149
+
name = "windows-sys"
6150
6150
+
version = "0.59.0"
6151
6151
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6152
6152
+
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
4110
6153
dependencies = [
4111
6154
"windows-targets 0.52.6",
4112
6155
]
···
4362
6405
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
4363
6406
4364
6407
[[package]]
6408
6408
+
name = "xml5ever"
6409
6409
+
version = "0.18.1"
6410
6410
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6411
6411
+
checksum = "9bbb26405d8e919bc1547a5aa9abc95cbfa438f04844f5fdd9dc7596b748bf69"
6412
6412
+
dependencies = [
6413
6413
+
"log",
6414
6414
+
"mac 0.1.1",
6415
6415
+
"markup5ever",
6416
6416
+
]
6417
6417
+
6418
6418
+
[[package]]
6419
6419
+
name = "y4m"
6420
6420
+
version = "0.8.0"
6421
6421
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6422
6422
+
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
6423
6423
+
6424
6424
+
[[package]]
4365
6425
name = "yansi"
4366
6426
version = "1.0.1"
4367
6427
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4436
6496
version = "1.8.2"
4437
6497
source = "registry+https://github.com/rust-lang/crates.io-index"
4438
6498
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
6499
6499
+
dependencies = [
6500
6500
+
"serde",
6501
6501
+
]
4439
6502
4440
6503
[[package]]
4441
6504
name = "zerotrie"
···
4503
6566
"cc",
4504
6567
"pkg-config",
4505
6568
]
6569
6569
+
6570
6570
+
[[package]]
6571
6571
+
name = "zune-core"
6572
6572
+
version = "0.5.1"
6573
6573
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6574
6574
+
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
6575
6575
+
6576
6576
+
[[package]]
6577
6577
+
name = "zune-inflate"
6578
6578
+
version = "0.2.54"
6579
6579
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6580
6580
+
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
6581
6581
+
dependencies = [
6582
6582
+
"simd-adler32",
6583
6583
+
]
6584
6584
+
6585
6585
+
[[package]]
6586
6586
+
name = "zune-jpeg"
6587
6587
+
version = "0.5.14"
6588
6588
+
source = "registry+https://github.com/rust-lang/crates.io-index"
6589
6589
+
checksum = "0b7a1c0af6e5d8d1363f4994b7a091ccf963d8b694f7da5b0b9cceb82da2c0a6"
6590
6590
+
dependencies = [
6591
6591
+
"zune-core",
6592
6592
+
]
···
1
1
[workspace]
2
2
resolver = "2"
3
3
-
members = ["crates/jacquard-common", "crates/jacquard-lexicon", "crates/jacquard-derive", "crates/jacquard-lexgen", "crates/jacquard-identity", "crates/jacquard-api"]
3
3
+
members = ["crates/*"]
4
4
5
5
6
6
[workspace.package]
···
166
166
}
167
167
}
168
168
169
169
-
impl FromStr for Did {
170
170
-
type Err = AtStrError;
171
171
-
172
172
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
173
173
-
Self::new_owned(s)
174
174
-
}
175
175
-
}
176
176
-
177
177
-
impl FromStr for Did<CowStr<'static>> {
178
178
-
type Err = AtStrError;
179
179
-
180
180
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
181
181
-
Self::new_owned(s)
182
182
-
}
183
183
-
}
184
184
-
185
185
-
impl FromStr for Did<String> {
169
169
+
impl<S: Bos<str> + FromStr> FromStr for Did<S> {
186
170
type Err = AtStrError;
187
171
188
172
fn from_str(s: &str) -> Result<Self, Self::Err> {
···
138
138
}
139
139
}
140
140
141
141
-
impl FromStr for Nsid {
142
142
-
type Err = AtStrError;
143
143
-
144
144
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
145
145
-
Self::new_owned(s)
146
146
-
}
147
147
-
}
148
148
-
149
149
-
impl FromStr for Nsid<CowStr<'static>> {
150
150
-
type Err = AtStrError;
151
151
-
152
152
-
fn from_str(s: &str) -> Result<Self, Self::Err> {
153
153
-
Self::new_owned(s)
154
154
-
}
155
155
-
}
156
156
-
157
157
-
impl FromStr for Nsid<String> {
141
141
+
impl<S: Bos<str> + FromStr> FromStr for Nsid<S> {
158
142
type Err = AtStrError;
159
143
160
144
fn from_str(s: &str) -> Result<Self, Self::Err> {
···
336
336
#[cfg(not(target_arch = "wasm32"))]
337
337
fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
338
338
where
339
339
-
R: XrpcRequest + Send + Sync,
339
339
+
R: XrpcRequest + Send + Sync + serde::Serialize,
340
340
<R as XrpcRequest>::Response: Send + Sync,
341
341
Self: Sync;
342
342
···
344
344
#[cfg(target_arch = "wasm32")]
345
345
fn send<R>(&self, request: R) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
346
346
where
347
347
-
R: XrpcRequest + Send + Sync,
347
347
+
R: XrpcRequest + Send + Sync + serde::Serialize,
348
348
<R as XrpcRequest>::Response: Send + Sync;
349
349
350
350
/// Send an XRPC request and parse the response
···
355
355
opts: CallOptions<'_>,
356
356
) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
357
357
where
358
358
-
R: XrpcRequest + Send + Sync,
358
358
+
R: XrpcRequest + Send + Sync + serde::Serialize,
359
359
<R as XrpcRequest>::Response: Send + Sync,
360
360
Self: Sync;
361
361
···
367
367
opts: CallOptions<'_>,
368
368
) -> impl Future<Output = XrpcResult<XrpcResponse<R>>>
369
369
where
370
370
-
R: XrpcRequest + Send + Sync,
370
370
+
R: XrpcRequest + Send + Sync + serde::Serialize,
371
371
<R as XrpcRequest>::Response: Send + Sync;
372
372
}
373
373
···
381
381
request: R,
382
382
) -> impl Future<Output = Result<StreamingResponse, StreamError>> + Send
383
383
where
384
384
-
R: XrpcRequest + Send + Sync,
384
384
+
R: XrpcRequest + Send + Sync + serde::Serialize,
385
385
<R as XrpcRequest>::Response: Send + Sync,
386
386
Self: Sync;
387
387
···
392
392
request: R,
393
393
) -> impl Future<Output = Result<StreamingResponse, StreamError>>
394
394
where
395
395
-
R: XrpcRequest + Send + Sync,
395
395
+
R: XrpcRequest + Send + Sync + serde::Serialize,
396
396
<R as XrpcRequest>::Response: Send + Sync;
397
397
398
398
/// Stream an XRPC procedure call and its response
···
1
1
+
use std::str::FromStr;
2
2
+
1
3
use crate::types::OAuthClientMetadata;
2
4
use crate::{keyset::Keyset, scopes::Scope};
3
3
-
use jacquard_common::cowstr::ToCowStr;
4
5
use jacquard_common::deps::fluent_uri::Uri;
5
5
-
use jacquard_common::{CowStr, IntoStatic};
6
6
+
use jacquard_common::{BosStr, IntoStatic};
6
7
use serde::{Deserialize, Serialize};
7
7
-
use smol_str::{SmolStr, ToSmolStr};
8
8
+
use smol_str::SmolStr;
8
9
use thiserror::Error;
9
10
10
11
/// Errors that can occur when building AT Protocol OAuth client metadata.
···
78
79
PrivateKeyJwt,
79
80
}
80
81
81
81
-
impl From<AuthMethod> for CowStr<'static> {
82
82
+
impl From<AuthMethod> for SmolStr {
83
83
+
fn from(value: AuthMethod) -> Self {
84
84
+
match value {
85
85
+
AuthMethod::None => SmolStr::new_static("none"),
86
86
+
AuthMethod::PrivateKeyJwt => SmolStr::new_static("private_key_jwt"),
87
87
+
}
88
88
+
}
89
89
+
}
90
90
+
91
91
+
impl From<AuthMethod> for &'static str {
82
92
fn from(value: AuthMethod) -> Self {
83
93
match value {
84
84
-
AuthMethod::None => CowStr::new_static("none"),
85
85
-
AuthMethod::PrivateKeyJwt => CowStr::new_static("private_key_jwt"),
94
94
+
AuthMethod::None => "none",
95
95
+
AuthMethod::PrivateKeyJwt => "private_key_jwt",
86
96
}
87
97
}
88
98
}
···
97
107
RefreshToken,
98
108
}
99
109
100
100
-
impl From<GrantType> for CowStr<'static> {
110
110
+
impl From<GrantType> for SmolStr {
101
111
fn from(value: GrantType) -> Self {
102
112
match value {
103
103
-
GrantType::AuthorizationCode => CowStr::new_static("authorization_code"),
104
104
-
GrantType::RefreshToken => CowStr::new_static("refresh_token"),
113
113
+
GrantType::AuthorizationCode => SmolStr::new_static("authorization_code"),
114
114
+
GrantType::RefreshToken => SmolStr::new_static("refresh_token"),
115
115
+
}
116
116
+
}
117
117
+
}
118
118
+
119
119
+
impl From<GrantType> for &'static str {
120
120
+
fn from(value: GrantType) -> Self {
121
121
+
match value {
122
122
+
GrantType::AuthorizationCode => "authorization_code",
123
123
+
GrantType::RefreshToken => "refresh_token",
105
124
}
106
125
}
107
126
}
···
113
132
/// typed fields for URIs and scopes rather than raw strings. Use [`atproto_client_metadata`]
114
133
/// to convert this into the wire format expected by OAuth servers.
115
134
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
116
116
-
pub struct AtprotoClientMetadata<'m> {
135
135
+
pub struct AtprotoClientMetadata<S: BosStr + FromStr + Ord>
136
136
+
where
137
137
+
<S as FromStr>::Err: core::fmt::Debug,
138
138
+
{
117
139
/// The unique identifier for this client, typically the URL of its metadata document.
118
140
pub client_id: Uri<String>,
119
141
/// The URI of the client's homepage or information page.
···
123
145
/// The grant types this client will use.
124
146
pub grant_types: Vec<GrantType>,
125
147
/// The OAuth scopes this client requests; must include `atproto`.
126
126
-
#[serde(borrow)]
127
127
-
pub scopes: Vec<Scope<'m>>,
148
148
+
pub scopes: Vec<Scope<S>>,
128
149
/// URI pointing to the client's JWK Set; mutually exclusive with inline `jwks`.
129
150
pub jwks_uri: Option<Uri<String>>,
130
151
/// Human-readable display name for the client.
131
131
-
pub client_name: Option<SmolStr>,
152
152
+
pub client_name: Option<S>,
132
153
/// URI of the client's logo image.
133
154
pub logo_uri: Option<Uri<String>>,
134
155
/// URI of the client's terms of service document.
···
137
158
pub privacy_policy_uri: Option<Uri<String>>,
138
159
}
139
160
140
140
-
impl<'m> IntoStatic for AtprotoClientMetadata<'m> {
141
141
-
type Output = AtprotoClientMetadata<'static>;
142
142
-
fn into_static(self) -> AtprotoClientMetadata<'static> {
161
161
+
impl<S> IntoStatic for AtprotoClientMetadata<S>
162
162
+
where
163
163
+
S: BosStr + IntoStatic + Ord + FromStr,
164
164
+
<S as FromStr>::Err: core::fmt::Debug,
165
165
+
S::Output: BosStr + FromStr + Ord,
166
166
+
<S::Output as FromStr>::Err: core::fmt::Debug,
167
167
+
{
168
168
+
type Output = AtprotoClientMetadata<S::Output>;
169
169
+
fn into_static(self) -> AtprotoClientMetadata<S::Output> {
143
170
AtprotoClientMetadata {
144
171
client_id: self.client_id,
145
172
client_uri: self.client_uri,
···
147
174
grant_types: self.grant_types,
148
175
scopes: self.scopes.into_static(),
149
176
jwks_uri: self.jwks_uri,
150
150
-
client_name: self.client_name,
177
177
+
client_name: self.client_name.into_static(),
151
178
logo_uri: self.logo_uri,
152
179
tos_uri: self.tos_uri,
153
180
privacy_policy_uri: None,
···
155
182
}
156
183
}
157
184
158
158
-
impl<'m> AtprotoClientMetadata<'m> {
185
185
+
impl<S> AtprotoClientMetadata<S>
186
186
+
where
187
187
+
S: BosStr + IntoStatic + Ord + FromStr,
188
188
+
<S as FromStr>::Err: core::fmt::Debug,
189
189
+
S::Output: BosStr + FromStr + Ord,
190
190
+
<S::Output as FromStr>::Err: core::fmt::Debug,
191
191
+
{
159
192
/// Attach optional production branding fields to the metadata.
160
193
///
161
194
/// Chainable builder method for setting display name, logo, and policy URLs after
162
195
/// constructing the base metadata.
163
196
pub fn with_prod_info(
164
197
mut self,
165
165
-
client_name: &str,
198
198
+
client_name: S,
166
199
logo_uri: Option<Uri<String>>,
167
200
tos_uri: Option<Uri<String>>,
168
201
privacy_policy_uri: Option<Uri<String>>,
169
202
) -> Self {
170
170
-
self.client_name = Some(client_name.to_smolstr());
203
203
+
self.client_name = Some(client_name);
171
204
self.logo_uri = logo_uri;
172
205
self.tos_uri = tos_uri;
173
206
self.privacy_policy_uri = privacy_policy_uri;
···
182
215
pub fn default_localhost() -> Self {
183
216
Self::new_localhost(
184
217
None,
185
185
-
Some(Scope::parse_multiple("atproto transition:generic").unwrap()),
218
218
+
Some(vec![
219
219
+
Scope::Atproto,
220
220
+
Scope::Transition(crate::scopes::TransitionScope::Generic),
221
221
+
]),
186
222
)
187
223
}
188
224
···
194
230
/// are used.
195
231
pub fn new_localhost(
196
232
redirect_uris: Option<Vec<Uri<String>>>,
197
197
-
scopes: Option<Vec<Scope<'static>>>,
198
198
-
) -> AtprotoClientMetadata<'static> {
233
233
+
scopes: Option<Vec<Scope<S>>>,
234
234
+
) -> AtprotoClientMetadata<S> {
199
235
// determine client_id
200
236
#[derive(serde::Serialize)]
201
201
-
struct Parameters<'a> {
237
237
+
struct Parameters {
202
238
#[serde(skip_serializing_if = "Option::is_none")]
203
203
-
redirect_uri: Option<Vec<CowStr<'a>>>,
239
239
+
redirect_uri: Option<Vec<SmolStr>>,
204
240
#[serde(skip_serializing_if = "Option::is_none")]
205
205
-
scope: Option<CowStr<'a>>,
241
241
+
scope: Option<SmolStr>,
206
242
}
207
243
let redir_str = redirect_uris.as_ref().map(|uris| {
208
244
uris.iter()
209
209
-
.map(|u| u.as_str().trim_end_matches("/").to_cowstr().into_static())
245
245
+
.map(|u| SmolStr::from(u.as_str().trim_end_matches("/")))
210
246
.collect()
211
247
});
212
248
let query = serde_html_form::to_string(Parameters {
213
249
redirect_uri: redir_str,
214
250
scope: scopes
215
251
.as_ref()
216
216
-
.map(|s| Scope::serialize_multiple(s.as_slice())),
252
252
+
.map(|s| SmolStr::from(Scope::serialize_multiple(s.as_slice()).as_str())),
217
253
})
218
254
.ok();
219
255
let mut client_id = String::from("http://localhost/");
···
246
282
/// selects the appropriate `token_endpoint_auth_method` based on whether a keyset is provided,
247
283
/// and serializes scopes and grant types into their string representations. Returns an error
248
284
/// if any required field is missing or invalid.
249
249
-
pub fn atproto_client_metadata<'m>(
250
250
-
metadata: AtprotoClientMetadata<'m>,
285
285
+
pub fn atproto_client_metadata<S>(
286
286
+
metadata: &AtprotoClientMetadata<S>,
251
287
keyset: &Option<Keyset>,
252
252
-
) -> Result<OAuthClientMetadata<'static>> {
288
288
+
) -> Result<OAuthClientMetadata<S>>
289
289
+
where
290
290
+
S: BosStr + Ord + FromStr + Clone,
291
291
+
<S as FromStr>::Err: core::fmt::Debug,
292
292
+
{
253
293
let is_loopback = metadata.client_id.scheme().as_str() == "http"
254
294
&& metadata.client_id.authority().map(|a| a.host()) == Some("localhost");
255
295
let application_type = if is_loopback {
256
256
-
Some(CowStr::new_static("native"))
296
296
+
Some(S::from_static("native"))
257
297
} else {
258
258
-
Some(CowStr::new_static("web"))
298
298
+
Some(S::from_static("web"))
259
299
};
260
300
if metadata.redirect_uris.is_empty() {
261
301
return Err(Error::EmptyRedirectUris);
···
272
312
} else {
273
313
None
274
314
};
275
275
-
(AuthMethod::PrivateKeyJwt, metadata.jwks_uri, jwks)
315
315
+
(AuthMethod::PrivateKeyJwt, metadata.jwks_uri.as_ref(), jwks)
276
316
} else {
277
317
(AuthMethod::None, None, None)
278
318
};
279
279
-
let client_id = metadata
280
280
-
.client_id
281
281
-
.as_str()
282
282
-
.trim_end_matches("/")
283
283
-
.to_string();
319
319
+
let client_id = metadata.client_id.as_str();
284
320
let client_uri = metadata
285
321
.client_uri
286
322
.as_ref()
287
287
-
.map(|u| u.as_str().trim_end_matches("/").to_string().into());
323
323
+
.and_then(|u| S::from_str(u.as_str()).ok());
288
324
let redirect_uris = metadata
289
325
.redirect_uris
290
326
.iter()
291
291
-
.map(|u| u.as_str().trim_end_matches("/").to_string().into())
327
327
+
.filter_map(|u| S::from_str(u.as_str()).ok())
292
328
.collect();
293
293
-
let jwks_uri = jwks_uri.map(|u| u.as_str().trim_end_matches("/").to_string().into());
329
329
+
let jwks_uri = jwks_uri.as_ref().and_then(|u| S::from_str(u.as_str()).ok());
294
330
Ok(OAuthClientMetadata {
295
295
-
client_id: client_id.into(),
331
331
+
client_id: S::from_str(client_id).unwrap(),
296
332
client_uri,
297
333
redirect_uris,
298
298
-
application_type,
299
299
-
token_endpoint_auth_method: Some(auth_method.into()),
300
300
-
grant_types: Some(metadata.grant_types.into_iter().map(|v| v.into()).collect()),
301
301
-
response_types: vec!["code".to_cowstr()],
302
302
-
scope: Some(Scope::serialize_multiple(metadata.scopes.as_slice())),
334
334
+
application_type: application_type,
335
335
+
token_endpoint_auth_method: Some(S::from_static(auth_method.into())),
336
336
+
grant_types: Some(
337
337
+
metadata
338
338
+
.grant_types
339
339
+
.iter()
340
340
+
.map(|v| S::from_static(v.clone().into()))
341
341
+
.collect(),
342
342
+
),
343
343
+
response_types: vec![S::from_static("code")],
344
344
+
scope: Some(
345
345
+
S::from_str(Scope::serialize_multiple(metadata.scopes.as_slice()).as_str()).unwrap(),
346
346
+
),
303
347
dpop_bound_access_tokens: Some(true),
304
348
jwks_uri,
305
349
jwks,
306
350
token_endpoint_auth_signing_alg: if keyset.is_some() {
307
307
-
Some(CowStr::new_static("ES256"))
351
351
+
Some(S::from_static("ES256"))
308
352
} else {
309
353
None
310
354
},
311
311
-
client_name: metadata.client_name,
355
355
+
client_name: metadata.client_name.as_ref().map(|c| c.clone()),
312
356
logo_uri: metadata
313
357
.logo_uri
314
358
.as_ref()
315
315
-
.map(|u| u.as_str().to_string().into()),
359
359
+
.and_then(|u| S::from_str(u.as_str()).ok()),
316
360
tos_uri: metadata
317
361
.tos_uri
318
362
.as_ref()
319
319
-
.map(|u| u.as_str().to_string().into()),
363
363
+
.and_then(|u| S::from_str(u.as_str()).ok()),
320
364
privacy_policy_uri: metadata
321
365
.privacy_policy_uri
322
366
.as_ref()
323
323
-
.map(|u| u.as_str().to_string().into()),
367
367
+
.and_then(|u| S::from_str(u.as_str()).ok()),
324
368
})
325
369
}
326
370
···
342
386
#[test]
343
387
fn test_localhost_client_metadata_default() {
344
388
assert_eq!(
345
345
-
atproto_client_metadata(AtprotoClientMetadata::new_localhost(None, None), &None)
389
389
+
atproto_client_metadata(&AtprotoClientMetadata::new_localhost(None, None), &None)
346
390
.unwrap(),
347
391
OAuthClientMetadata {
348
348
-
client_id: CowStr::new_static("http://localhost"),
392
392
+
client_id: SmolStr::new_static("http://localhost/"),
349
393
client_uri: None,
350
394
redirect_uris: vec![
351
351
-
CowStr::new_static("http://127.0.0.1"),
352
352
-
CowStr::new_static("http://[::1]"),
395
395
+
SmolStr::new_static("http://127.0.0.1"),
396
396
+
SmolStr::new_static("http://[::1]"),
353
397
],
354
354
-
application_type: Some(CowStr::new_static("native")),
355
355
-
scope: Some(CowStr::new_static("atproto")),
398
398
+
application_type: Some(SmolStr::new_static("native")),
399
399
+
scope: Some(SmolStr::new_static("atproto")),
356
400
grant_types: Some(vec![
357
357
-
"authorization_code".to_cowstr(),
358
358
-
"refresh_token".to_cowstr()
401
401
+
SmolStr::new_static("authorization_code"),
402
402
+
SmolStr::new_static("refresh_token")
359
403
]),
360
360
-
response_types: vec!["code".to_cowstr()],
404
404
+
response_types: vec![SmolStr::new_static("code")],
361
405
token_endpoint_auth_method: Some(AuthMethod::None.into()),
362
406
dpop_bound_access_tokens: Some(true),
363
407
jwks_uri: None,
···
375
419
fn test_localhost_client_metadata_custom() {
376
420
assert_eq!(
377
421
atproto_client_metadata(
378
378
-
AtprotoClientMetadata::new_localhost(
422
422
+
&AtprotoClientMetadata::new_localhost(
379
423
Some(vec![
380
424
Uri::parse("http://127.0.0.1/callback".to_string()).unwrap(),
381
425
Uri::parse("http://[::1]/callback".to_string()).unwrap(),
···
390
434
)
391
435
.expect("failed to convert metadata"),
392
436
OAuthClientMetadata {
393
393
-
client_id: CowStr::new_static(
437
437
+
client_id: SmolStr::new_static(
394
438
"http://localhost/?redirect_uri=http%3A%2F%2F127.0.0.1%2Fcallback&redirect_uri=http%3A%2F%2F%5B%3A%3A1%5D%2Fcallback&scope=account%3Aemail+atproto+transition%3Ageneric"
395
439
),
396
440
client_uri: None,
397
441
redirect_uris: vec![
398
398
-
CowStr::new_static("http://127.0.0.1/callback"),
399
399
-
CowStr::new_static("http://[::1]/callback"),
442
442
+
SmolStr::new_static("http://127.0.0.1/callback"),
443
443
+
SmolStr::new_static("http://[::1]/callback"),
400
444
],
401
401
-
scope: Some(CowStr::new_static(
445
445
+
scope: Some(SmolStr::new_static(
402
446
"account:email atproto transition:generic"
403
447
)),
404
404
-
application_type: Some(CowStr::new_static("native")),
448
448
+
application_type: Some(SmolStr::new_static("native")),
405
449
grant_types: Some(vec![
406
406
-
"authorization_code".to_cowstr(),
407
407
-
"refresh_token".to_cowstr()
450
450
+
SmolStr::new_static("authorization_code"),
451
451
+
SmolStr::new_static("refresh_token")
408
452
]),
409
409
-
response_types: vec!["code".to_cowstr()],
453
453
+
response_types: vec![SmolStr::new_static("code")],
410
454
token_endpoint_auth_method: Some(AuthMethod::None.into()),
411
455
dpop_bound_access_tokens: Some(true),
412
456
jwks_uri: None,
···
425
469
// Invalid inputs are coerced to http://localhost rather than failing
426
470
{
427
471
let out = atproto_client_metadata(
428
428
-
AtprotoClientMetadata::new_localhost(
472
472
+
&AtprotoClientMetadata::new_localhost(
429
473
Some(vec![Uri::parse("https://127.0.0.1".to_string()).unwrap()]),
430
474
None,
431
475
),
···
435
479
assert_eq!(
436
480
out,
437
481
OAuthClientMetadata {
438
438
-
client_id: CowStr::new_static(
482
482
+
client_id: SmolStr::new_static(
439
483
"http://localhost/?redirect_uri=https%3A%2F%2F127.0.0.1"
440
484
),
441
441
-
application_type: Some(CowStr::new_static("native")),
485
485
+
application_type: Some(SmolStr::new_static("native")),
442
486
client_uri: None,
443
443
-
redirect_uris: vec![CowStr::new_static("https://127.0.0.1")],
444
444
-
scope: Some(CowStr::new_static("atproto")),
487
487
+
redirect_uris: vec![SmolStr::new_static("https://127.0.0.1")],
488
488
+
scope: Some(SmolStr::new_static("atproto")),
445
489
grant_types: Some(vec![
446
446
-
"authorization_code".to_cowstr(),
447
447
-
"refresh_token".to_cowstr()
490
490
+
SmolStr::new_static("authorization_code"),
491
491
+
SmolStr::new_static("refresh_token")
448
492
]),
449
449
-
response_types: vec!["code".to_cowstr()],
493
493
+
response_types: vec![SmolStr::new_static("code")],
450
494
token_endpoint_auth_method: Some(AuthMethod::None.into()),
451
495
dpop_bound_access_tokens: Some(true),
452
496
jwks_uri: None,
···
461
505
}
462
506
{
463
507
let out = atproto_client_metadata(
464
464
-
AtprotoClientMetadata::new_localhost(
508
508
+
&AtprotoClientMetadata::new_localhost(
465
509
Some(vec![
466
510
Uri::parse("http://localhost:8000".to_string()).unwrap(),
467
511
]),
···
473
517
assert_eq!(
474
518
out,
475
519
OAuthClientMetadata {
476
476
-
client_id: CowStr::new_static(
520
520
+
client_id: SmolStr::new_static(
477
521
"http://localhost/?redirect_uri=http%3A%2F%2Flocalhost%3A8000"
478
522
),
479
523
client_uri: None,
480
480
-
redirect_uris: vec![CowStr::new_static("http://localhost:8000")],
481
481
-
scope: Some(CowStr::new_static("atproto")),
524
524
+
redirect_uris: vec![SmolStr::new_static("http://localhost:8000")],
525
525
+
scope: Some(SmolStr::new_static("atproto")),
482
526
grant_types: Some(vec![
483
483
-
"authorization_code".to_cowstr(),
484
484
-
"refresh_token".to_cowstr()
527
527
+
SmolStr::new_static("authorization_code"),
528
528
+
SmolStr::new_static("refresh_token")
485
529
]),
486
486
-
application_type: Some(CowStr::new_static("native")),
487
487
-
response_types: vec!["code".to_cowstr()],
530
530
+
application_type: Some(SmolStr::new_static("native")),
531
531
+
response_types: vec![SmolStr::new_static("code")],
488
532
token_endpoint_auth_method: Some(AuthMethod::None.into()),
489
533
dpop_bound_access_tokens: Some(true),
490
534
jwks_uri: None,
···
499
543
}
500
544
{
501
545
let out = atproto_client_metadata(
502
502
-
AtprotoClientMetadata::new_localhost(
546
546
+
&AtprotoClientMetadata::new_localhost(
503
547
Some(vec![Uri::parse("http://192.168.0.0/".to_string()).unwrap()]),
504
548
None,
505
549
),
···
509
553
assert_eq!(
510
554
out,
511
555
OAuthClientMetadata {
512
512
-
client_id: CowStr::new_static(
556
556
+
client_id: SmolStr::new_static(
513
557
"http://localhost/?redirect_uri=http%3A%2F%2F192.168.0.0"
514
558
),
515
559
client_uri: None,
516
516
-
redirect_uris: vec![CowStr::new_static("http://192.168.0.0")],
517
517
-
scope: Some(CowStr::new_static("atproto")),
560
560
+
redirect_uris: vec![SmolStr::new_static("http://192.168.0.0/")],
561
561
+
scope: Some(SmolStr::new_static("atproto")),
518
562
grant_types: Some(vec![
519
519
-
"authorization_code".to_cowstr(),
520
520
-
"refresh_token".to_cowstr()
563
563
+
SmolStr::new_static("authorization_code"),
564
564
+
SmolStr::new_static("refresh_token")
521
565
]),
522
522
-
application_type: Some(CowStr::new_static("native")),
523
523
-
response_types: vec!["code".to_cowstr()],
566
566
+
application_type: Some(SmolStr::new_static("native")),
567
567
+
response_types: vec![SmolStr::new_static("code")],
524
568
token_endpoint_auth_method: Some(AuthMethod::None.into()),
525
569
dpop_bound_access_tokens: Some(true),
526
570
jwks_uri: None,
···
552
596
{
553
597
// Non-loopback clients without a keyset should fail (must provide JWKS)
554
598
let metadata = metadata.clone();
555
555
-
let err = atproto_client_metadata(metadata, &None);
599
599
+
let err = atproto_client_metadata(&metadata, &None);
556
600
assert!(err.is_ok());
557
601
}
558
602
{
···
568
612
}];
569
613
let keyset = Keyset::try_from(keys.clone()).expect("failed to create keyset");
570
614
assert_eq!(
571
571
-
atproto_client_metadata(metadata, &Some(keyset.clone()))
615
615
+
atproto_client_metadata(&metadata, &Some(keyset.clone()))
572
616
.expect("failed to convert metadata"),
573
617
OAuthClientMetadata {
574
574
-
client_id: CowStr::new_static("https://example.com/client_metadata.json"),
575
575
-
client_uri: Some(CowStr::new_static("https://example.com")),
576
576
-
redirect_uris: vec![CowStr::new_static("https://example.com/callback")],
577
577
-
application_type: Some(CowStr::new_static("web")),
578
578
-
scope: Some(CowStr::new_static("atproto")),
579
579
-
grant_types: Some(vec![CowStr::new_static("authorization_code")]),
618
618
+
client_id: SmolStr::new_static("https://example.com/client_metadata.json"),
619
619
+
client_uri: Some(SmolStr::new_static("https://example.com")),
620
620
+
redirect_uris: vec![SmolStr::new_static("https://example.com/callback")],
621
621
+
application_type: Some(SmolStr::new_static("web")),
622
622
+
scope: Some(SmolStr::new_static("atproto")),
623
623
+
grant_types: Some(vec![SmolStr::new_static("authorization_code")]),
580
624
token_endpoint_auth_method: Some(AuthMethod::PrivateKeyJwt.into()),
581
625
dpop_bound_access_tokens: Some(true),
582
582
-
response_types: vec!["code".to_cowstr()],
626
626
+
response_types: vec![SmolStr::new_static("code")],
583
627
jwks_uri: None,
584
628
jwks: Some(keyset.public_jwks()),
585
585
-
token_endpoint_auth_signing_alg: Some(CowStr::new_static("ES256")),
629
629
+
token_endpoint_auth_signing_alg: Some(SmolStr::new_static("ES256")),
586
630
client_name: None,
587
631
logo_uri: None,
588
632
tos_uri: None,
···
3
3
4
4
use dashmap::DashMap;
5
5
use jacquard_common::{
6
6
-
IntoStatic,
6
6
+
bos::BosStr,
7
7
session::{SessionStore, SessionStoreError},
8
8
types::did::Did,
9
9
};
10
10
-
use smol_str::{SmolStr, ToSmolStr, format_smolstr};
10
10
+
use smol_str::{SmolStr, format_smolstr};
11
11
12
12
use crate::session::{AuthRequestData, ClientSessionData};
13
13
···
20
20
#[cfg_attr(not(target_arch = "wasm32"), trait_variant::make(Send))]
21
21
pub trait ClientAuthStore {
22
22
/// Retrieve an active session for the given DID and session identifier, if one exists.
23
23
-
fn get_session(
23
23
+
fn get_session<D: BosStr + Send + Sync>(
24
24
&self,
25
25
-
did: &Did<'_>,
25
25
+
did: &Did<D>,
26
26
session_id: &str,
27
27
-
) -> impl Future<Output = Result<Option<ClientSessionData<'_>>, SessionStoreError>>;
27
27
+
) -> impl Future<Output = Result<Option<ClientSessionData>, SessionStoreError>>;
28
28
29
29
/// Insert or update a session, replacing any existing entry for the same DID and session ID.
30
30
fn upsert_session(
31
31
&self,
32
32
-
session: ClientSessionData<'_>,
32
32
+
session: ClientSessionData,
33
33
) -> impl Future<Output = Result<(), SessionStoreError>>;
34
34
35
35
/// Delete the session for the given DID and session identifier.
36
36
-
fn delete_session(
36
36
+
fn delete_session<D: BosStr + Send + Sync>(
37
37
&self,
38
38
-
did: &Did<'_>,
38
38
+
did: &Did<D>,
39
39
session_id: &str,
40
40
) -> impl Future<Output = Result<(), SessionStoreError>>;
41
41
···
43
43
fn get_auth_req_info(
44
44
&self,
45
45
state: &str,
46
46
-
) -> impl Future<Output = Result<Option<AuthRequestData<'_>>, SessionStoreError>>;
46
46
+
) -> impl Future<Output = Result<Option<AuthRequestData>, SessionStoreError>>;
47
47
48
48
/// Persist authorization request data so it can be retrieved after the OAuth redirect.
49
49
fn save_auth_req_info(
50
50
&self,
51
51
-
auth_req_info: &AuthRequestData<'_>,
51
51
+
auth_req_info: &AuthRequestData,
52
52
) -> impl Future<Output = Result<(), SessionStoreError>>;
53
53
54
54
/// Remove authorization request data after the callback has been handled.
···
61
61
/// An in-memory implementation of [`ClientAuthStore`], suitable for testing and single-process
62
62
/// deployments where session persistence across restarts is not required.
63
63
pub struct MemoryAuthStore {
64
64
-
sessions: DashMap<SmolStr, ClientSessionData<'static>>,
65
65
-
auth_reqs: DashMap<SmolStr, AuthRequestData<'static>>,
64
64
+
sessions: DashMap<SmolStr, ClientSessionData>,
65
65
+
auth_reqs: DashMap<SmolStr, AuthRequestData>,
66
66
}
67
67
68
68
impl MemoryAuthStore {
···
76
76
}
77
77
78
78
impl ClientAuthStore for MemoryAuthStore {
79
79
-
async fn get_session(
79
79
+
async fn get_session<D: BosStr + Send + Sync>(
80
80
&self,
81
81
-
did: &Did<'_>,
81
81
+
did: &Did<D>,
82
82
session_id: &str,
83
83
-
) -> Result<Option<ClientSessionData<'_>>, SessionStoreError> {
83
83
+
) -> Result<Option<ClientSessionData>, SessionStoreError> {
84
84
let key = format_smolstr!("{}_{}", did, session_id);
85
85
Ok(self.sessions.get(&key).map(|v| v.clone()))
86
86
}
87
87
88
88
-
async fn upsert_session(
89
89
-
&self,
90
90
-
session: ClientSessionData<'_>,
91
91
-
) -> Result<(), SessionStoreError> {
88
88
+
async fn upsert_session(&self, session: ClientSessionData) -> Result<(), SessionStoreError> {
92
89
let key = format_smolstr!("{}_{}", session.account_did, session.session_id);
93
93
-
self.sessions.insert(key, session.into_static());
90
90
+
self.sessions.insert(key, session);
94
91
Ok(())
95
92
}
96
93
97
97
-
async fn delete_session(
94
94
+
async fn delete_session<D: BosStr + Send + Sync>(
98
95
&self,
99
99
-
did: &Did<'_>,
96
96
+
did: &Did<D>,
100
97
session_id: &str,
101
98
) -> Result<(), SessionStoreError> {
102
99
let key = format_smolstr!("{}_{}", did, session_id);
···
107
104
async fn get_auth_req_info(
108
105
&self,
109
106
state: &str,
110
110
-
) -> Result<Option<AuthRequestData<'_>>, SessionStoreError> {
107
107
+
) -> Result<Option<AuthRequestData>, SessionStoreError> {
111
108
Ok(self.auth_reqs.get(state).map(|v| v.clone()))
112
109
}
113
110
114
111
async fn save_auth_req_info(
115
112
&self,
116
116
-
auth_req_info: &AuthRequestData<'_>,
113
113
+
auth_req_info: &AuthRequestData,
117
114
) -> Result<(), SessionStoreError> {
118
118
-
self.auth_reqs.insert(
119
119
-
auth_req_info.state.clone().to_smolstr(),
120
120
-
auth_req_info.clone().into_static(),
121
121
-
);
115
115
+
self.auth_reqs
116
116
+
.insert(auth_req_info.state.clone(), auth_req_info.clone());
122
117
Ok(())
123
118
}
124
119
···
128
123
}
129
124
}
130
125
131
131
-
impl<T: ClientAuthStore + Send + Sync>
132
132
-
SessionStore<(Did<'static>, SmolStr), ClientSessionData<'static>> for Arc<T>
133
133
-
{
126
126
+
impl<T: ClientAuthStore + Send + Sync> SessionStore<(Did, SmolStr), ClientSessionData> for Arc<T> {
134
127
/// Get the current session if present.
135
135
-
async fn get(&self, key: &(Did<'static>, SmolStr)) -> Option<ClientSessionData<'static>> {
128
128
+
async fn get(&self, key: &(Did, SmolStr)) -> Option<ClientSessionData> {
136
129
let (did, session_id) = key;
137
130
self.as_ref()
138
131
.get_session(did, session_id)
139
132
.await
140
133
.ok()
141
134
.flatten()
142
142
-
.into_static()
143
135
}
144
136
/// Persist the given session.
145
137
async fn set(
146
138
&self,
147
147
-
_key: (Did<'static>, SmolStr),
148
148
-
session: ClientSessionData<'static>,
139
139
+
_key: (Did, SmolStr),
140
140
+
session: ClientSessionData,
149
141
) -> Result<(), SessionStoreError> {
150
142
self.as_ref().upsert_session(session).await
151
143
}
152
144
/// Delete the given session.
153
153
-
async fn del(&self, key: &(Did<'static>, SmolStr)) -> Result<(), SessionStoreError> {
145
145
+
async fn del(&self, key: &(Did, SmolStr)) -> Result<(), SessionStoreError> {
154
146
let (did, session_id) = key;
155
147
self.as_ref().delete_session(did, session_id).await
156
148
}
···
11
11
};
12
12
use jacquard_common::{
13
13
AuthorizationToken, CowStr, IntoStatic,
14
14
-
cowstr::ToCowStr,
14
14
+
bos::BosStr,
15
15
deps::fluent_uri::Uri,
16
16
error::{AuthError, ClientError, XrpcResult},
17
17
http_client::HttpClient,
18
18
types::{did::Did, string::Handle},
19
19
xrpc::{
20
20
-
CallOptions, Response, XrpcClient, XrpcError, XrpcExt, XrpcRequest, XrpcResp, XrpcResponse,
20
20
+
CallOptions, Response, XrpcClient, XrpcExt, XrpcRequest, XrpcResp, XrpcResponse,
21
21
build_http_request, process_response,
22
22
},
23
23
};
···
31
31
resolver::{DidDocResponse, IdentityError, IdentityResolver, ResolverOptions},
32
32
};
33
33
use jose_jwk::JwkSet;
34
34
-
use std::{future::Future, sync::Arc};
34
34
+
use smol_str::{SmolStr, ToSmolStr};
35
35
+
use std::{str::FromStr, sync::Arc};
35
36
use tokio::sync::RwLock;
36
37
37
38
/// The top-level OAuth client responsible for driving the authorization flow.
···
41
42
S: ClientAuthStore,
42
43
{
43
44
/// Shared session registry that mediates access to the backing auth store.
44
44
-
pub registry: Arc<SessionRegistry<T, S>>,
45
45
+
pub registry: Arc<SessionRegistry<T, S, SmolStr>>,
45
46
/// Default call options applied to every outgoing XRPC request.
46
47
pub options: RwLock<CallOptions<'static>>,
47
48
/// Override for the XRPC base URI; falls back to the public Bluesky AppView when `None`.
···
52
53
53
54
impl<S: ClientAuthStore> OAuthClient<JacquardResolver, S> {
54
55
/// Create an `OAuthClient` using the default [`JacquardResolver`] for identity and metadata resolution.
55
55
-
pub fn new(store: S, client_data: ClientData<'static>) -> Self {
56
56
+
pub fn new(store: S, client_data: ClientData<SmolStr>) -> Self {
56
57
let client = JacquardResolver::default();
57
58
Self::new_from_resolver(store, client, client_data)
58
59
}
···
110
111
S: ClientAuthStore,
111
112
{
112
113
/// Create an OAuth client from an explicit resolver instance, taking ownership of both.
113
113
-
pub fn new_from_resolver(store: S, client: T, client_data: ClientData<'static>) -> Self {
114
114
+
pub fn new_from_resolver(store: S, client: T, client_data: ClientData<SmolStr>) -> Self {
114
115
// #[cfg(feature = "tracing")]
115
116
// tracing::info!(
116
117
// redirect_uris = ?client_data.config.redirect_uris,
···
133
134
pub fn new_with_shared(
134
135
store: Arc<S>,
135
136
client: Arc<T>,
136
136
-
client_data: ClientData<'static>,
137
137
+
client_data: ClientData<SmolStr>,
137
138
) -> Self {
138
139
let registry = Arc::new(SessionRegistry::new_shared(
139
140
store,
···
172
173
///
173
174
/// The caller is responsible for redirecting the user's browser to the returned URL.
174
175
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip(self, input), fields(input = input.as_ref())))]
175
175
-
pub async fn start_auth(
176
176
+
pub async fn start_auth<Str: BosStr>(
176
177
&self,
177
178
input: impl AsRef<str>,
178
178
-
options: AuthorizeOptions<'_>,
179
179
-
) -> Result<String> {
179
179
+
options: AuthorizeOptions<Str>,
180
180
+
) -> Result<String>
181
181
+
where
182
182
+
Str: FromStr + Ord + Clone + core::fmt::Debug,
183
183
+
<Str as FromStr>::Err: core::fmt::Debug,
184
184
+
{
180
185
let client_metadata = atproto_client_metadata(
181
181
-
self.registry.client_data.config.clone(),
186
186
+
&self.registry.client_data.config,
182
187
&self.registry.client_data.keyset,
183
188
)?;
184
189
let (server_metadata, identity) = self.client.resolve_oauth(input.as_ref()).await?;
···
187
192
} else {
188
193
None
189
194
};
190
190
-
let metadata = OAuthMetadata {
195
195
+
let mut metadata = OAuthMetadata {
191
196
server_metadata,
192
197
client_metadata,
193
198
keyset: self.registry.client_data.keyset.clone(),
···
197
202
self.client.as_ref(),
198
203
login_hint,
199
204
options.prompt,
200
200
-
&metadata,
201
201
-
options.state,
205
205
+
&mut metadata,
206
206
+
options.state.map(|s| s.as_ref().to_smolstr()),
202
207
)
203
208
.await?;
204
209
···
209
214
.await?;
210
215
211
216
#[derive(serde::Serialize)]
212
212
-
struct Parameters<'s> {
213
213
-
client_id: CowStr<'s>,
214
214
-
request_uri: CowStr<'s>,
217
217
+
struct Parameters {
218
218
+
client_id: smol_str::SmolStr,
219
219
+
request_uri: smol_str::SmolStr,
215
220
}
216
221
Ok(metadata.server_metadata.authorization_endpoint.to_string()
217
222
+ "?"
···
227
232
/// Validates the `state` and optional `iss` parameters, exchanges the authorization code for
228
233
/// tokens via the token endpoint, verifies the `sub` claim against the expected issuer, and
229
234
/// persists the resulting session. On success returns an [`OAuthSession`] ready for API calls.
230
230
-
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", skip_all, fields(state = params.state.as_ref().map(|s| s.as_ref()))))]
231
231
-
pub async fn callback(&self, params: CallbackParams<'_>) -> Result<OAuthSession<T, S>> {
235
235
+
#[cfg_attr(feature = "tracing", tracing::instrument(level = "info", skip_all, fields(state = params.state.as_ref().map(|s| s.as_str()))))]
236
236
+
pub async fn callback(&self, params: CallbackParams) -> Result<OAuthSession<T, S>> {
232
237
let Some(state_key) = params.state else {
233
238
return Err(CallbackError::MissingState.into());
234
239
};
235
240
236
236
-
let Some(auth_req_info) = self.registry.store.get_auth_req_info(&state_key).await? else {
241
241
+
let Some(auth_req_info) = self
242
242
+
.registry
243
243
+
.store
244
244
+
.get_auth_req_info(state_key.as_str())
245
245
+
.await?
246
246
+
else {
237
247
return Err(CallbackError::MissingState.into());
238
248
};
239
249
240
240
-
self.registry.store.delete_auth_req_info(&state_key).await?;
250
250
+
self.registry
251
251
+
.store
252
252
+
.delete_auth_req_info(state_key.as_str())
253
253
+
.await?;
241
254
242
255
let metadata = self
243
256
.client
244
244
-
.get_authorization_server_metadata(&auth_req_info.authserver_url.to_cowstr())
257
257
+
.get_authorization_server_metadata(auth_req_info.authserver_url.as_str())
245
258
.await?;
246
259
247
260
if let Some(iss) = params.iss {
···
258
271
let metadata = OAuthMetadata {
259
272
server_metadata: metadata,
260
273
client_metadata: atproto_client_metadata(
261
261
-
self.registry.client_data.config.clone(),
274
274
+
&self.registry.client_data.config,
262
275
&self.registry.client_data.keyset,
263
276
)?,
264
277
keyset: self.registry.client_data.keyset.clone(),
···
268
281
match exchange_code(
269
282
self.client.as_ref(),
270
283
&mut auth_req_info.dpop_data.clone(),
271
271
-
¶ms.code,
272
272
-
&auth_req_info.pkce_verifier,
284
284
+
params.code.as_str(),
285
285
+
auth_req_info.pkce_verifier.as_str(),
273
286
&metadata,
274
287
)
275
288
.await
276
289
{
277
290
Ok(token_set) => {
278
291
let scopes = if let Some(scope) = &token_set.scope {
279
279
-
Scope::parse_multiple_reduced(&scope)
292
292
+
Scope::<SmolStr>::parse_multiple_reduced(scope.as_str())
280
293
.expect("Failed to parse scopes")
281
294
.into_static()
282
295
} else {
···
285
298
let client_data = ClientSessionData {
286
299
account_did: token_set.sub.clone(),
287
300
session_id: auth_req_info.state,
288
288
-
host_url: Uri::parse(token_set.aud.as_ref())?.to_owned(),
289
289
-
authserver_url: auth_req_info.authserver_url.to_cowstr(),
301
301
+
host_url: Uri::parse(token_set.aud.as_str())?.to_owned(),
302
302
+
authserver_url: auth_req_info.authserver_url,
290
303
authserver_token_endpoint: auth_req_info.authserver_token_endpoint,
291
304
authserver_revocation_endpoint: auth_req_info.authserver_revocation_endpoint,
292
305
scopes,
293
306
dpop_data: DpopClientData {
294
307
dpop_key: auth_req_info.dpop_data.dpop_key.clone(),
295
295
-
dpop_authserver_nonce: authserver_nonce.unwrap_or(CowStr::default()),
308
308
+
dpop_authserver_nonce: authserver_nonce.unwrap_or_default(),
296
309
dpop_host_nonce: auth_req_info
297
310
.dpop_data
298
311
.dpop_authserver_nonce
299
299
-
.unwrap_or(CowStr::default()),
312
312
+
.unwrap_or_default(),
300
313
},
301
314
token_set,
302
315
};
···
307
320
}
308
321
}
309
322
310
310
-
async fn create_session(&self, data: ClientSessionData<'_>) -> Result<OAuthSession<T, S>> {
323
323
+
async fn create_session(&self, data: ClientSessionData) -> Result<OAuthSession<T, S>> {
311
324
self.registry.set(data.clone()).await?;
312
325
Ok(OAuthSession::new(
313
326
self.registry.clone(),
···
317
330
}
318
331
319
332
/// Restore a previously created session from the backing store, refreshing tokens if needed.
320
320
-
pub async fn restore(&self, did: &Did<'_>, session_id: &str) -> Result<OAuthSession<T, S>> {
333
333
+
pub async fn restore(
334
334
+
&self,
335
335
+
did: &Did<impl BosStr + Send + Sync>,
336
336
+
session_id: &str,
337
337
+
) -> Result<OAuthSession<T, S>> {
321
338
self.create_session(self.registry.get(did, session_id, true).await?)
322
339
.await
323
340
}
···
327
344
/// Note: this removes the session from local storage but does **not** call the authorization
328
345
/// server's revocation endpoint. To also invalidate the token server-side, prefer
329
346
/// [`OAuthSession::logout`], which calls `revoke` on the token before deleting the session.
330
330
-
pub async fn revoke(&self, did: &Did<'_>, session_id: &str) -> Result<()> {
347
347
+
pub async fn revoke(
348
348
+
&self,
349
349
+
did: &Did<impl BosStr + Send + Sync>,
350
350
+
session_id: &str,
351
351
+
) -> Result<()> {
331
352
Ok(self.registry.del(did, session_id).await?)
332
353
}
333
354
}
···
356
377
self.client.options()
357
378
}
358
379
359
359
-
async fn resolve_handle(
380
380
+
async fn resolve_handle<Str: BosStr + Sync>(
360
381
&self,
361
361
-
handle: &Handle<'_>,
362
362
-
) -> jacquard_identity::resolver::Result<Did<'static>> {
382
382
+
handle: &Handle<Str>,
383
383
+
) -> jacquard_identity::resolver::Result<Did> {
363
384
self.client.resolve_handle(handle).await
364
385
}
365
386
366
366
-
async fn resolve_did_doc(
387
387
+
async fn resolve_did_doc<Str: BosStr + Sync>(
367
388
&self,
368
368
-
did: &Did<'_>,
389
389
+
did: &Did<Str>,
369
390
) -> jacquard_identity::resolver::Result<DidDocResponse> {
370
391
self.client.resolve_did_doc(did).await
371
392
}
···
401
422
402
423
async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
403
424
where
404
404
-
R: XrpcRequest + Send + Sync,
425
425
+
R: XrpcRequest + Send + Sync + serde::Serialize,
405
426
<R as XrpcRequest>::Response: Send + Sync,
406
427
{
407
428
let opts = self.options.read().await.clone();
···
414
435
opts: CallOptions<'_>,
415
436
) -> XrpcResult<XrpcResponse<R>>
416
437
where
417
417
-
R: XrpcRequest + Send + Sync,
438
438
+
R: XrpcRequest + Send + Sync + serde::Serialize,
418
439
<R as XrpcRequest>::Response: Send + Sync,
419
440
{
420
441
let base_uri = self.base_uri().await;
421
421
-
self.client
422
422
-
.xrpc(base_uri)
423
423
-
.with_options(opts.clone())
424
424
-
.send(&request)
442
442
+
let http_request = build_http_request(&base_uri.borrow(), &request, &opts)?;
443
443
+
let http_response = self
444
444
+
.client
445
445
+
.send_http(http_request)
425
446
.await
447
447
+
.map_err(|e| ClientError::transport(e).for_nsid(R::NSID))?;
448
448
+
process_response(http_response)
426
449
}
427
450
}
428
451
···
439
462
S: ClientAuthStore,
440
463
{
441
464
/// Shared registry used to persist and retrieve session data across refresh operations.
442
442
-
pub registry: Arc<SessionRegistry<T, S>>,
465
465
+
pub registry: Arc<SessionRegistry<T, S, SmolStr>>,
443
466
/// Underlying HTTP/identity/OAuth resolver shared with the parent `OAuthClient`.
444
467
pub client: Arc<T>,
445
468
/// Optional WebSocket client; `()` when WebSocket support is not required.
446
469
pub ws_client: W,
447
470
/// Mutable session data including DPoP key, nonces, and token set.
448
448
-
pub data: RwLock<ClientSessionData<'static>>,
471
471
+
pub data: RwLock<ClientSessionData>,
449
472
/// Default call options applied to every outgoing XRPC request from this session.
450
473
pub options: RwLock<CallOptions<'static>>,
451
474
}
···
460
483
/// This is the standard constructor used by [`OAuthClient::callback`] and
461
484
/// [`OAuthClient::restore`]. For WebSocket support use [`OAuthSession::new_with_ws`].
462
485
pub fn new(
463
463
-
registry: Arc<SessionRegistry<T, S>>,
486
486
+
registry: Arc<SessionRegistry<T, S, SmolStr>>,
464
487
client: Arc<T>,
465
465
-
data: ClientSessionData<'static>,
488
488
+
data: ClientSessionData,
466
489
) -> Self {
467
490
Self {
468
491
registry,
···
485
508
/// to standard XRPC calls. The `ws_client` is exposed via [`OAuthSession::ws_client`] and
486
509
/// is used by the `WebSocketClient` impl when the `websocket` feature is enabled.
487
510
pub fn new_with_ws(
488
488
-
registry: Arc<SessionRegistry<T, S>>,
511
511
+
registry: Arc<SessionRegistry<T, S, SmolStr>>,
489
512
client: Arc<T>,
490
513
ws_client: W,
491
491
-
data: ClientSessionData<'static>,
514
514
+
data: ClientSessionData,
492
515
) -> Self {
493
516
Self {
494
517
registry,
···
527
550
///
528
551
/// The session ID is the random `state` token generated during the PAR flow and can
529
552
/// be used together with the DID to restore the session via [`OAuthClient::restore`].
530
530
-
pub async fn session_info(&self) -> (Did<'_>, CowStr<'_>) {
553
553
+
pub async fn session_info(&self) -> (Did, smol_str::SmolStr) {
531
554
let data = self.data.read().await;
532
555
(data.account_did.clone(), data.session_id.clone())
533
556
}
···
541
564
///
542
565
/// The token may be stale if it has expired; use [`OAuthSession::refresh`] or
543
566
/// rely on the automatic refresh performed by `send_with_opts` to obtain a fresh one.
544
544
-
pub async fn access_token(&self) -> AuthorizationToken<'_> {
545
545
-
AuthorizationToken::Dpop(self.data.read().await.token_set.access_token.clone())
567
567
+
pub async fn access_token(&self) -> AuthorizationToken<'static> {
568
568
+
AuthorizationToken::Dpop(CowStr::Owned(
569
569
+
self.data.read().await.token_set.access_token.clone(),
570
570
+
))
546
571
}
547
572
548
573
/// Return the current refresh token for this session, if one is present.
549
574
///
550
575
/// Not all authorization servers issue refresh tokens. When `None` is returned,
551
576
/// the session cannot be silently renewed and the user must re-authenticate.
552
552
-
pub async fn refresh_token(&self) -> Option<AuthorizationToken<'_>> {
577
577
+
pub async fn refresh_token(&self) -> Option<AuthorizationToken<'static>> {
553
578
self.data
554
579
.read()
555
580
.await
556
581
.token_set
557
582
.refresh_token
558
558
-
.as_ref()
559
559
-
.map(|t| AuthorizationToken::Dpop(t.clone()))
583
583
+
.clone()
584
584
+
.map(|t| AuthorizationToken::Dpop(CowStr::Owned(t)))
560
585
}
561
586
562
587
/// Derive an unauthenticated [`OAuthClient`] that shares the same registry and resolver.
···
628
653
/// The actual token exchange is serialized per `(DID, session_id)` pair via a `Mutex` inside
629
654
/// the registry, so concurrent refresh attempts will not result in duplicate token exchanges.
630
655
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))]
631
631
-
pub async fn refresh(&self) -> Result<AuthorizationToken<'_>> {
656
656
+
pub async fn refresh(&self) -> Result<AuthorizationToken<'static>> {
632
657
// Read identifiers without holding the lock across await
633
658
let (did, sid) = {
634
659
let data = self.data.read().await;
635
660
(data.account_did.clone(), data.session_id.clone())
636
661
};
637
662
let refreshed = self.registry.as_ref().get(&did, &sid, true).await?;
638
638
-
let token = AuthorizationToken::Dpop(refreshed.token_set.access_token.clone());
663
663
+
let token =
664
664
+
AuthorizationToken::Dpop(CowStr::Owned(refreshed.token_set.access_token.clone()));
639
665
// Write back updated session
640
666
*self.data.write().await = refreshed.clone().into_static();
641
667
// Store in the registry
···
687
713
688
714
async fn send<R>(&self, request: R) -> XrpcResult<XrpcResponse<R>>
689
715
where
690
690
-
R: XrpcRequest + Send + Sync,
716
716
+
R: XrpcRequest + Send + Sync + serde::Serialize,
691
717
<R as XrpcRequest>::Response: Send + Sync,
692
718
{
693
719
let opts = self.options.read().await.clone();
···
700
726
mut opts: CallOptions<'_>,
701
727
) -> XrpcResult<XrpcResponse<R>>
702
728
where
703
703
-
R: XrpcRequest + Send + Sync,
729
729
+
R: XrpcRequest + Send + Sync + serde::Serialize,
704
730
<R as XrpcRequest>::Response: Send + Sync,
705
731
{
706
732
let base_uri = self.base_uri().await;
···
711
737
let http_response = self
712
738
.client
713
739
.dpop_call(&mut dpop)
714
714
-
.send(build_http_request(&base_uri, &request, &opts)?)
740
740
+
.send(build_http_request(&base_uri.borrow(), &request, &opts)?)
715
741
.await
716
742
.map_err(|e| ClientError::from(e).for_nsid(R::NSID))?;
717
743
let resp = process_response(http_response);
···
741
767
let http_response = self
742
768
.client
743
769
.dpop_call(&mut dpop)
744
744
-
.send(build_http_request(&base_uri, &request, &opts)?)
770
770
+
.send(build_http_request(&base_uri.borrow(), &request, &opts)?)
745
771
.await
746
772
.map_err(|e| {
747
773
ClientError::from(e)
···
832
858
request: R,
833
859
) -> core::result::Result<jacquard_common::xrpc::StreamingResponse, jacquard_common::StreamError>
834
860
where
835
835
-
R: XrpcRequest + Send + Sync,
861
861
+
R: XrpcRequest + Send + Sync + serde::Serialize,
836
862
<R as XrpcRequest>::Response: Send + Sync,
837
863
{
838
864
use jacquard_common::StreamError;
···
840
866
let base_uri = <Self as XrpcClient>::base_uri(self).await;
841
867
let mut opts = self.options.read().await.clone();
842
868
opts.auth = Some(self.access_token().await);
843
843
-
let http_request = build_http_request(&base_uri, &request, &opts)
869
869
+
let http_request = build_http_request(&base_uri.borrow(), &request, &opts)
844
870
.map_err(|e| StreamError::protocol(e.to_string()))?;
845
871
let guard = self.data.read().await;
846
872
let mut dpop = guard.dpop_data.clone();
···
860
886
.await
861
887
.map_err(|e| StreamError::transport(e))?,
862
888
);
863
863
-
let http_request = build_http_request(&base_uri, &request, &opts)
889
889
+
let http_request = build_http_request(&base_uri.borrow(), &request, &opts)
864
890
.map_err(|e| StreamError::protocol(e.to_string()))?;
865
891
let guard = self.data.read().await;
866
892
let mut dpop = guard.dpop_data.clone();
···
976
1002
.is_ok_and(|s| s.starts_with("DPoP ") && s.contains("error=\"invalid_token\"")),
977
1003
_ => false,
978
1004
},
979
979
-
Ok(resp) => match resp.parse() {
980
980
-
Err(XrpcError::Auth(AuthError::InvalidToken)) => true,
981
981
-
_ => false,
982
982
-
},
1005
1005
+
Ok(_) => false,
983
1006
}
984
1007
}
985
1008
···
993
1016
self.client.options()
994
1017
}
995
1018
996
996
-
fn resolve_handle(
1019
1019
+
async fn resolve_handle<Str: BosStr + Sync>(
997
1020
&self,
998
998
-
handle: &Handle<'_>,
999
999
-
) -> impl Future<Output = std::result::Result<Did<'static>, IdentityError>> {
1000
1000
-
async { self.client.resolve_handle(handle).await }
1021
1021
+
handle: &Handle<Str>,
1022
1022
+
) -> std::result::Result<Did, IdentityError> {
1023
1023
+
self.client.resolve_handle(handle).await
1001
1024
}
1002
1025
1003
1003
-
fn resolve_did_doc(
1026
1026
+
async fn resolve_did_doc<Str: BosStr + Sync>(
1004
1027
&self,
1005
1005
-
did: &Did<'_>,
1006
1006
-
) -> impl Future<Output = std::result::Result<DidDocResponse, IdentityError>> {
1007
1007
-
async { self.client.resolve_did_doc(did).await }
1028
1028
+
did: &Did<Str>,
1029
1029
+
) -> std::result::Result<DidDocResponse, IdentityError> {
1030
1030
+
self.client.resolve_did_doc(did).await
1008
1031
}
1009
1032
}
1010
1033
···
1061
1084
params: &Sub,
1062
1085
) -> std::result::Result<jacquard_common::xrpc::SubscriptionStream<Sub::Stream>, Self::Error>
1063
1086
where
1064
1064
-
Sub: XrpcSubscription + Send + Sync,
1087
1087
+
Sub: XrpcSubscription + Send + Sync + serde::Serialize,
1065
1088
{
1066
1089
let opts = self.subscription_opts().await;
1067
1090
self.subscribe_with_opts(params, opts).await
···
1073
1096
opts: jacquard_common::xrpc::SubscriptionOptions<'_>,
1074
1097
) -> std::result::Result<jacquard_common::xrpc::SubscriptionStream<Sub::Stream>, Self::Error>
1075
1098
where
1076
1076
-
Sub: XrpcSubscription + Send + Sync,
1099
1099
+
Sub: XrpcSubscription + Send + Sync + serde::Serialize,
1077
1100
{
1078
1101
use jacquard_common::xrpc::SubscriptionExt;
1079
1102
let base = self.base_uri().await;
···
5
5
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
6
6
use chrono::Utc;
7
7
use http::{Request, Response, header::InvalidHeaderValue};
8
8
-
use jacquard_common::{CowStr, IntoStatic, cowstr::ToCowStr, http_client::HttpClient};
8
8
+
use jacquard_common::http_client::HttpClient;
9
9
use jacquard_identity::JacquardResolver;
10
10
use jose_jwa::{Algorithm, Signing};
11
11
use jose_jwk::{Jwk, Key, crypto};
12
12
use p256::ecdsa::SigningKey;
13
13
use rand::{RngCore, SeedableRng};
14
14
use sha2::Digest;
15
15
-
use smol_str::SmolStr;
15
15
+
use smol_str::{SmolStr, ToSmolStr};
16
16
17
17
use crate::{
18
18
jose::{
···
461
461
}
462
462
}
463
463
464
464
-
/// Extract authorization hash from request headers
465
465
-
fn extract_ath(headers: &http::HeaderMap) -> Option<CowStr<'static>> {
464
464
+
/// Extract authorization hash from request headers.
465
465
+
fn extract_ath(headers: &http::HeaderMap) -> Option<SmolStr> {
466
466
headers
467
467
.get("authorization")
468
468
.filter(|v| v.to_str().is_ok_and(|s| s.starts_with("DPoP ")))
469
469
.map(|auth| {
470
470
-
URL_SAFE_NO_PAD
471
471
-
.encode(sha2::Sha256::digest(&auth.as_bytes()[5..]))
472
472
-
.into()
470
470
+
SmolStr::new(URL_SAFE_NO_PAD.encode(sha2::Sha256::digest(&auth.as_bytes()[5..])))
473
471
})
474
472
}
475
473
476
476
-
/// Get nonce from data source based on target
477
477
-
fn get_nonce<N: DpopDataSource>(data_source: &N, is_to_auth_server: bool) -> Option<CowStr<'_>> {
474
474
+
/// Get nonce from data source based on target, returning an owned copy of the current nonce.
475
475
+
///
476
476
+
/// Returning an owned `SmolStr` rather than a borrow ensures callers can later take a
477
477
+
/// mutable reference to the data source (e.g., to call `store_nonce`) without violating
478
478
+
/// Rust's aliasing rules.
479
479
+
fn get_nonce<N: DpopDataSource>(data_source: &N, is_to_auth_server: bool) -> Option<SmolStr> {
478
480
if is_to_auth_server {
479
479
-
data_source.authserver_nonce()
481
481
+
data_source.authserver_nonce().map(SmolStr::new)
480
482
} else {
481
481
-
data_source.host_nonce()
483
483
+
data_source.host_nonce().map(SmolStr::new)
482
484
}
483
485
}
484
486
485
485
-
/// Store nonce in data source based on target
486
486
-
fn store_nonce<N: DpopDataSource>(
487
487
-
data_source: &mut N,
488
488
-
is_to_auth_server: bool,
489
489
-
nonce: CowStr<'static>,
490
490
-
) {
487
487
+
/// Store nonce in data source based on target.
488
488
+
fn store_nonce<N: DpopDataSource>(data_source: &mut N, is_to_auth_server: bool, nonce: SmolStr) {
491
489
if is_to_auth_server {
492
490
data_source.set_authserver_nonce(nonce);
493
491
} else {
···
515
513
} else {
516
514
DpopTarget::ResourceServer
517
515
};
518
518
-
let uri = request.uri().clone();
519
519
-
let method = request.method().to_cowstr().into_static();
520
520
-
let url_str: SmolStr = uri.to_cowstr().as_ref().into();
521
521
-
let uri = uri.to_cowstr();
516
516
+
let method = request.method().to_smolstr();
517
517
+
let uri = request.uri().to_smolstr();
522
518
let ath = extract_ath(request.headers());
523
519
524
520
let init_nonce = get_nonce(data_source, is_to_auth_server);
525
521
let init_proof = build_dpop_proof(
526
522
data_source.key(),
527
527
-
method.clone(),
528
528
-
uri.clone(),
529
529
-
init_nonce.clone(),
530
530
-
ath.clone(),
523
523
+
&method,
524
524
+
&uri,
525
525
+
init_nonce.as_deref(),
526
526
+
ath.as_deref(),
531
527
)?;
532
532
-
request.headers_mut().insert("DPoP", init_proof.parse()?);
528
528
+
request
529
529
+
.headers_mut()
530
530
+
.insert("DPoP", init_proof.as_str().parse()?);
533
531
let response = client
534
532
.send_http(request.clone())
535
533
.await
536
536
-
.map_err(|e| DpopError::transport(target, url_str.clone(), e))?;
534
534
+
.map_err(|e| DpopError::transport(target, uri.clone(), e))?;
537
535
538
538
-
let next_nonce = response
536
536
+
let next_nonce: Option<SmolStr> = response
539
537
.headers()
540
538
.get("dpop-nonce")
541
539
.and_then(|v| v.to_str().ok())
542
542
-
.map(|c| CowStr::copy_from_str(c));
540
540
+
.map(SmolStr::new);
543
541
match &next_nonce {
544
544
-
Some(s) if next_nonce != init_nonce => {
545
545
-
store_nonce(data_source, is_to_auth_server, s.clone());
542
542
+
Some(_) if next_nonce.as_deref() != init_nonce.as_deref() => {
543
543
+
store_nonce(data_source, is_to_auth_server, next_nonce.clone().unwrap());
546
544
}
547
545
_ => {
548
546
return Ok(response);
···
552
550
if !is_use_dpop_nonce_error(is_to_auth_server, &response) {
553
551
return Ok(response);
554
552
}
555
555
-
let next_proof = build_dpop_proof(data_source.key(), method, uri, next_nonce, ath)?;
556
556
-
request.headers_mut().insert("DPoP", next_proof.parse()?);
553
553
+
let next_proof = build_dpop_proof(
554
554
+
data_source.key(),
555
555
+
&method,
556
556
+
&uri,
557
557
+
next_nonce.as_deref(),
558
558
+
ath.as_deref(),
559
559
+
)?;
560
560
+
request
561
561
+
.headers_mut()
562
562
+
.insert("DPoP", next_proof.as_str().parse()?);
557
563
let response = client
558
564
.send_http(request)
559
565
.await
560
560
-
.map_err(|e| DpopError::nonce_retry(target, url_str, e))?;
566
566
+
.map_err(|e| DpopError::nonce_retry(target, uri.clone(), e))?;
561
567
Ok(response)
562
568
}
563
569
···
584
590
} else {
585
591
DpopTarget::ResourceServer
586
592
};
587
587
-
let uri = request.uri().clone();
588
588
-
let method = request.method().to_cowstr().into_static();
589
589
-
let url_str: SmolStr = uri.to_cowstr().as_ref().into();
590
590
-
let uri = uri.to_cowstr();
593
593
+
let method = request.method().to_smolstr();
594
594
+
let uri = request.uri().to_smolstr();
591
595
let ath = extract_ath(request.headers());
592
596
593
597
let init_nonce = get_nonce(data_source, is_to_auth_server);
594
598
let init_proof = build_dpop_proof(
595
599
data_source.key(),
596
596
-
method.clone(),
597
597
-
uri.clone(),
598
598
-
init_nonce.clone(),
599
599
-
ath.clone(),
600
600
+
&method,
601
601
+
&uri,
602
602
+
init_nonce.as_deref(),
603
603
+
ath.as_deref(),
600
604
)?;
601
601
-
request.headers_mut().insert("DPoP", init_proof.parse()?);
605
605
+
request
606
606
+
.headers_mut()
607
607
+
.insert("DPoP", init_proof.as_str().parse()?);
602
608
let http_response = client
603
609
.send_http_streaming(request.clone())
604
610
.await
605
605
-
.map_err(|e| DpopError::transport(target, url_str.clone(), e))?;
611
611
+
.map_err(|e| DpopError::transport(target, uri.clone(), e))?;
606
612
607
613
let (parts, body) = http_response.into_parts();
608
608
-
let next_nonce = parts
614
614
+
let next_nonce: Option<SmolStr> = parts
609
615
.headers
610
616
.get("DPoP-Nonce")
611
617
.and_then(|v| v.to_str().ok())
612
612
-
.map(|c| CowStr::from(c.to_string()));
618
618
+
.map(SmolStr::new);
613
619
match &next_nonce {
614
614
-
Some(s) if next_nonce != init_nonce => {
615
615
-
store_nonce(data_source, is_to_auth_server, s.clone());
620
620
+
Some(_) if next_nonce.as_deref() != init_nonce.as_deref() => {
621
621
+
store_nonce(data_source, is_to_auth_server, next_nonce.clone().unwrap());
616
622
}
617
623
_ => {
618
624
return Ok(StreamingResponse::new(parts, body));
619
625
}
620
626
}
621
627
622
622
-
// For streaming responses, we can't easily check the body for use_dpop_nonce error
623
623
-
// We check status code + headers only
628
628
+
// For streaming responses, we can't easily check the body for use_dpop_nonce error.
629
629
+
// We check status code + headers only.
624
630
if !is_use_dpop_nonce_error_streaming(is_to_auth_server, parts.status, &parts.headers) {
625
631
return Ok(StreamingResponse::new(parts, body));
626
632
}
627
633
628
628
-
let next_proof = build_dpop_proof(data_source.key(), method, uri, next_nonce, ath)?;
629
629
-
request.headers_mut().insert("DPoP", next_proof.parse()?);
634
634
+
let next_proof = build_dpop_proof(
635
635
+
data_source.key(),
636
636
+
&method,
637
637
+
&uri,
638
638
+
next_nonce.as_deref(),
639
639
+
ath.as_deref(),
640
640
+
)?;
641
641
+
request
642
642
+
.headers_mut()
643
643
+
.insert("DPoP", next_proof.as_str().parse()?);
630
644
let http_response = client
631
645
.send_http_streaming(request)
632
646
.await
633
633
-
.map_err(|e| DpopError::nonce_retry(target, url_str, e))?;
647
647
+
.map_err(|e| DpopError::nonce_retry(target, uri, e))?;
634
648
let (parts, body) = http_response.into_parts();
635
649
Ok(StreamingResponse::new(parts, body))
636
650
}
···
658
672
} else {
659
673
DpopTarget::ResourceServer
660
674
};
661
661
-
let uri = parts.uri.clone();
662
662
-
let method = parts.method.to_cowstr().into_static();
663
663
-
let url_str: SmolStr = uri.to_cowstr().as_ref().into();
664
664
-
let uri = uri.to_cowstr();
675
675
+
let method = parts.method.to_smolstr();
676
676
+
let uri = parts.uri.to_smolstr();
665
677
let ath = extract_ath(&parts.headers);
666
678
667
679
let init_nonce = get_nonce(data_source, is_to_auth_server);
668
680
let init_proof = build_dpop_proof(
669
681
data_source.key(),
670
670
-
method.clone(),
671
671
-
uri.clone(),
672
672
-
init_nonce.clone(),
673
673
-
ath.clone(),
682
682
+
&method,
683
683
+
&uri,
684
684
+
init_nonce.as_deref(),
685
685
+
ath.as_deref(),
674
686
)?;
675
675
-
parts.headers.insert("DPoP", init_proof.parse()?);
687
687
+
parts.headers.insert("DPoP", init_proof.as_str().parse()?);
676
688
677
677
-
// Clone the stream for potential retry
689
689
+
// Clone the stream for potential retry.
678
690
let (body1, body2) = body.tee();
679
691
680
692
let http_response = client
681
693
.send_http_bidirectional(parts.clone(), body1.into_inner())
682
694
.await
683
683
-
.map_err(|e| DpopError::transport(target, url_str.clone(), e))?;
695
695
+
.map_err(|e| DpopError::transport(target, uri.clone(), e))?;
684
696
685
697
let (resp_parts, resp_body) = http_response.into_parts();
686
686
-
let next_nonce = resp_parts
698
698
+
let next_nonce: Option<SmolStr> = resp_parts
687
699
.headers
688
700
.get("DPoP-Nonce")
689
701
.and_then(|v| v.to_str().ok())
690
690
-
.map(|c| CowStr::from(c.to_string()));
702
702
+
.map(SmolStr::new);
691
703
match &next_nonce {
692
692
-
Some(s) if next_nonce != init_nonce => {
693
693
-
store_nonce(data_source, is_to_auth_server, s.clone());
704
704
+
Some(_) if next_nonce.as_deref() != init_nonce.as_deref() => {
705
705
+
store_nonce(data_source, is_to_auth_server, next_nonce.clone().unwrap());
694
706
}
695
707
_ => {
696
708
return Ok(StreamingResponse::new(resp_parts, resp_body));
697
709
}
698
710
}
699
711
700
700
-
// For streaming responses, we can't easily check the body for use_dpop_nonce error
701
701
-
// We check status code + headers only
712
712
+
// For streaming responses, we can't easily check the body for use_dpop_nonce error.
713
713
+
// We check status code + headers only.
702
714
if !is_use_dpop_nonce_error_streaming(is_to_auth_server, resp_parts.status, &resp_parts.headers)
703
715
{
704
716
return Ok(StreamingResponse::new(resp_parts, resp_body));
705
717
}
706
718
707
707
-
let next_proof = build_dpop_proof(data_source.key(), method, uri, next_nonce, ath)?;
708
708
-
parts.headers.insert("DPoP", next_proof.parse()?);
719
719
+
let next_proof = build_dpop_proof(
720
720
+
data_source.key(),
721
721
+
&method,
722
722
+
&uri,
723
723
+
next_nonce.as_deref(),
724
724
+
ath.as_deref(),
725
725
+
)?;
726
726
+
parts.headers.insert("DPoP", next_proof.as_str().parse()?);
709
727
let http_response = client
710
728
.send_http_bidirectional(parts, body2.into_inner())
711
729
.await
712
712
-
.map_err(|e| DpopError::nonce_retry(target, url_str, e))?;
730
730
+
.map_err(|e| DpopError::nonce_retry(target, uri, e))?;
713
731
let (parts, body) = http_response.into_parts();
714
732
Ok(StreamingResponse::new(parts, body))
715
733
}
···
760
778
}
761
779
762
780
#[inline]
763
763
-
pub(crate) fn generate_jti() -> CowStr<'static> {
781
781
+
pub(crate) fn generate_jti() -> SmolStr {
764
782
let mut rng = rand::rngs::SmallRng::from_entropy();
765
783
let mut bytes = [0u8; 12];
766
784
rng.fill_bytes(&mut bytes);
···
769
787
770
788
/// Build a compact JWS (ES256) for DPoP with embedded public JWK.
771
789
#[inline]
772
772
-
pub fn build_dpop_proof<'s>(
790
790
+
pub fn build_dpop_proof(
773
791
key: &Key,
774
774
-
method: CowStr<'s>,
775
775
-
url: CowStr<'s>,
776
776
-
nonce: Option<CowStr<'s>>,
777
777
-
ath: Option<CowStr<'s>>,
778
778
-
) -> Result<CowStr<'s>> {
792
792
+
method: &str,
793
793
+
url: &str,
794
794
+
nonce: Option<&str>,
795
795
+
ath: Option<&str>,
796
796
+
) -> Result<SmolStr> {
779
797
let secret = match crypto::Key::try_from(key).map_err(DpopError::crypto)? {
780
798
crypto::Key::P256(crypto::Kind::Secret(sk)) => sk,
781
799
_ => return Err(DpopError::unsupported_key()),
782
800
};
783
783
-
let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::Es256));
784
784
-
header.typ = Some(JWT_HEADER_TYP_DPOP.into());
801
801
+
let mut header: RegisteredHeader<&str> =
802
802
+
RegisteredHeader::from(Algorithm::Signing(Signing::Es256));
803
803
+
header.typ = Some(JWT_HEADER_TYP_DPOP);
785
804
header.jwk = Some(Jwk {
786
805
key: Key::from(&crypto::Key::from(secret.public_key())),
787
806
prm: Default::default(),
788
807
});
789
808
790
790
-
let claims = Claims {
809
809
+
let claims: Claims<&str> = Claims {
791
810
registered: RegisteredClaims {
792
811
jti: Some(generate_jti()),
793
812
iat: Some(Utc::now().timestamp()),
···
796
815
public: PublicClaims {
797
816
htm: Some(method),
798
817
htu: Some(url),
799
799
-
ath: ath,
800
800
-
nonce: nonce,
818
818
+
ath,
819
819
+
nonce,
801
820
},
802
821
};
803
822
Ok(signing::create_signed_jwt_es256(
···
5
5
/// Signed JWT creation for supported algorithms (ES256, ES384, ES256K, EdDSA).
6
6
pub mod signing;
7
7
8
8
+
use jacquard_common::bos::{BosStr, DefaultStr};
8
9
use serde::{Deserialize, Serialize};
9
10
10
11
/// A JOSE header, covering the supported JWS formats.
···
12
13
/// Serialized as an untagged enum so the wire format matches the relevant JOSE spec directly.
13
14
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14
15
#[serde(untagged)]
15
15
-
pub enum Header<'a> {
16
16
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
17
17
+
pub enum Header<S: BosStr = DefaultStr> {
16
18
/// A JWS compact-serialization header.
17
17
-
#[serde(borrow)]
18
18
-
Jws(jws::Header<'a>),
19
19
+
Jws(jws::Header<S>),
19
20
}
20
21
···
1
1
-
use jacquard_common::{CowStr, IntoStatic};
1
1
+
use jacquard_common::{IntoStatic, bos::{BosStr, DefaultStr}};
2
2
use jose_jwa::Algorithm;
3
3
use jose_jwk::Jwk;
4
4
use serde::{Deserialize, Serialize};
5
5
6
6
/// A JWS compact-serialization header, wrapping the registered header fields.
7
7
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
8
8
-
pub struct Header<'a> {
8
8
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
9
9
+
pub struct Header<S: BosStr = DefaultStr> {
9
10
/// The registered header parameters defined by the JWS specification.
10
11
#[serde(flatten)]
11
11
-
#[serde(borrow)]
12
12
-
pub registered: RegisteredHeader<'a>,
12
12
+
pub registered: RegisteredHeader<S>,
13
13
}
14
14
15
15
-
impl<'a> From<Header<'a>> for super::super::jose::Header<'a> {
16
16
-
fn from(header: Header<'a>) -> Self {
15
15
+
impl<S: BosStr> From<Header<S>> for super::super::jose::Header<S> {
16
16
+
fn from(header: Header<S>) -> Self {
17
17
super::super::jose::Header::Jws(header)
18
18
}
19
19
}
20
20
21
21
/// Registered JWS header parameters as defined in RFC 7515 §4.1.
22
22
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
23
23
-
24
24
-
pub struct RegisteredHeader<'a> {
23
23
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
24
24
+
pub struct RegisteredHeader<S: BosStr = DefaultStr> {
25
25
/// The cryptographic algorithm used to sign the JWS (e.g., `ES256`).
26
26
pub alg: Algorithm,
27
27
/// JWK Set URL: a URI pointing to a resource containing the public key(s) used to sign the JWS.
28
28
-
#[serde(borrow)]
29
28
#[serde(skip_serializing_if = "Option::is_none")]
30
30
-
pub jku: Option<CowStr<'a>>,
29
29
+
pub jku: Option<S>,
31
30
/// JSON Web Key: the public key used to verify the JWS, embedded directly in the header.
32
31
#[serde(skip_serializing_if = "Option::is_none")]
33
32
pub jwk: Option<Jwk>,
34
33
/// Key ID: a hint indicating which key was used to sign the JWS.
35
34
#[serde(skip_serializing_if = "Option::is_none")]
36
36
-
pub kid: Option<CowStr<'a>>,
35
35
+
pub kid: Option<S>,
37
36
/// X.509 URL: a URI pointing to a resource for the X.509 certificate used to sign the JWS.
38
37
#[serde(skip_serializing_if = "Option::is_none")]
39
39
-
pub x5u: Option<CowStr<'a>>,
38
38
+
pub x5u: Option<S>,
40
39
/// X.509 certificate chain: the certificate (and chain) corresponding to the key used to sign the JWS.
41
40
#[serde(skip_serializing_if = "Option::is_none")]
42
42
-
pub x5c: Option<CowStr<'a>>,
41
41
+
pub x5c: Option<S>,
43
42
/// X.509 certificate SHA-1 thumbprint: base64url-encoded SHA-1 digest of the DER-encoded certificate.
44
43
#[serde(skip_serializing_if = "Option::is_none")]
45
45
-
pub x5t: Option<CowStr<'a>>,
44
44
+
pub x5t: Option<S>,
46
45
/// X.509 certificate SHA-256 thumbprint: base64url-encoded SHA-256 digest of the DER-encoded certificate.
47
46
#[serde(skip_serializing_if = "Option::is_none")]
48
47
#[serde(rename = "x5t#S256")]
49
49
-
pub x5ts256: Option<CowStr<'a>>,
48
48
+
pub x5ts256: Option<S>,
50
49
/// Type: declares the media type of the complete JWS, used by applications to disambiguate among JOSe objects.
51
50
#[serde(skip_serializing_if = "Option::is_none")]
52
52
-
pub typ: Option<CowStr<'a>>,
51
51
+
pub typ: Option<S>,
53
52
/// Content type: declares the media type of the secured content (the payload).
54
53
#[serde(skip_serializing_if = "Option::is_none")]
55
55
-
pub cty: Option<CowStr<'a>>,
54
54
+
pub cty: Option<S>,
56
55
}
57
56
58
58
-
impl From<Algorithm> for RegisteredHeader<'_> {
57
57
+
impl<S: BosStr> From<Algorithm> for RegisteredHeader<S> {
59
58
fn from(alg: Algorithm) -> Self {
60
59
Self {
61
60
alg,
···
72
71
}
73
72
}
74
73
75
75
-
impl<'a> From<RegisteredHeader<'a>> for super::super::jose::Header<'a> {
76
76
-
fn from(registered: RegisteredHeader<'a>) -> Self {
74
74
+
impl<S: BosStr> From<RegisteredHeader<S>> for super::super::jose::Header<S> {
75
75
+
fn from(registered: RegisteredHeader<S>) -> Self {
77
76
super::super::jose::Header::Jws(Header { registered })
78
77
}
79
78
}
80
79
81
81
-
impl IntoStatic for RegisteredHeader<'_> {
82
82
-
type Output = RegisteredHeader<'static>;
80
80
+
impl<S> IntoStatic for RegisteredHeader<S>
81
81
+
where
82
82
+
S: BosStr + IntoStatic,
83
83
+
S::Output: BosStr,
84
84
+
{
85
85
+
type Output = RegisteredHeader<S::Output>;
83
86
fn into_static(self) -> Self::Output {
84
87
RegisteredHeader {
85
88
alg: self.alg,
···
95
98
}
96
99
}
97
100
}
101
101
+
102
102
+
impl<S> IntoStatic for Header<S>
103
103
+
where
104
104
+
S: BosStr + IntoStatic,
105
105
+
S::Output: BosStr,
106
106
+
{
107
107
+
type Output = Header<S::Output>;
108
108
+
fn into_static(self) -> Self::Output {
109
109
+
Header {
110
110
+
registered: self.registered.into_static(),
111
111
+
}
112
112
+
}
113
113
+
}
···
1
1
-
use jacquard_common::{CowStr, IntoStatic};
1
1
+
use jacquard_common::{
2
2
+
IntoStatic,
3
3
+
bos::{BosStr, DefaultStr},
4
4
+
};
2
5
use serde::{Deserialize, Serialize};
6
6
+
use smol_str::SmolStr;
3
7
4
8
/// Full JWT claims payload, combining registered and public (DPoP-specific) claims.
5
5
-
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
6
6
-
pub struct Claims<'a> {
9
9
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10
10
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
11
11
+
pub struct Claims<S: BosStr = DefaultStr> {
7
12
/// Standard registered JWT claims (iss, sub, aud, exp, etc.).
8
13
#[serde(flatten)]
9
9
-
pub registered: RegisteredClaims<'a>,
14
14
+
pub registered: RegisteredClaims<S>,
10
15
/// Public claims used in DPoP proofs (htm, htu, ath, nonce).
11
16
#[serde(flatten)]
12
12
-
#[serde(borrow)]
13
13
-
pub public: PublicClaims<'a>,
17
17
+
pub public: PublicClaims<S>,
18
18
+
}
19
19
+
20
20
+
/// Manual `Default` impl to avoid a spurious `S: Default` bound from the derive macro.
21
21
+
///
22
22
+
/// All `S`-typed fields are wrapped in `Option<S>`, which is `Default` regardless of `S`.
23
23
+
impl<S: BosStr> Default for Claims<S> {
24
24
+
fn default() -> Self {
25
25
+
Self {
26
26
+
registered: RegisteredClaims::default(),
27
27
+
public: PublicClaims::default(),
28
28
+
}
29
29
+
}
14
30
}
15
31
16
32
/// Standard registered JWT claims as defined in RFC 7519 §4.1.
17
17
-
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
18
18
-
19
19
-
pub struct RegisteredClaims<'a> {
33
33
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34
34
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
35
35
+
pub struct RegisteredClaims<S: BosStr = DefaultStr> {
20
36
/// Issuer: identifies the principal that issued the JWT.
21
21
-
#[serde(borrow)]
22
37
#[serde(skip_serializing_if = "Option::is_none")]
23
23
-
pub iss: Option<CowStr<'a>>,
38
38
+
pub iss: Option<S>,
24
39
/// Subject: identifies the principal that is the subject of the JWT.
25
40
#[serde(skip_serializing_if = "Option::is_none")]
26
26
-
pub sub: Option<CowStr<'a>>,
41
41
+
pub sub: Option<S>,
27
42
/// Audience: recipients that the JWT is intended for.
28
43
#[serde(skip_serializing_if = "Option::is_none")]
29
29
-
pub aud: Option<RegisteredClaimsAud<'a>>,
44
44
+
pub aud: Option<RegisteredClaimsAud<S>>,
30
45
/// Expiration time (Unix timestamp): the JWT must not be accepted on or after this time.
31
46
#[serde(skip_serializing_if = "Option::is_none")]
32
47
pub exp: Option<i64>,
···
38
53
pub iat: Option<i64>,
39
54
/// JWT ID: unique identifier for the token, used to prevent replay attacks.
40
55
#[serde(skip_serializing_if = "Option::is_none")]
41
41
-
pub jti: Option<CowStr<'a>>,
56
56
+
pub jti: Option<SmolStr>,
57
57
+
}
58
58
+
59
59
+
/// Manual `Default` impl to avoid a spurious `S: Default` bound from the derive macro.
60
60
+
///
61
61
+
/// All `S`-typed fields are wrapped in `Option<S>`, which is `Default` regardless of `S`.
62
62
+
impl<S: BosStr> Default for RegisteredClaims<S> {
63
63
+
fn default() -> Self {
64
64
+
Self {
65
65
+
iss: None,
66
66
+
sub: None,
67
67
+
aud: None,
68
68
+
exp: None,
69
69
+
nbf: None,
70
70
+
iat: None,
71
71
+
jti: None,
72
72
+
}
73
73
+
}
42
74
}
43
75
44
76
/// Public claims used in DPoP proof JWTs (RFC 9449).
45
77
///
46
78
/// These claims bind the DPoP proof to a specific HTTP request, preventing
47
79
/// the proof from being replayed against a different endpoint or method.
48
48
-
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
49
49
-
50
50
-
pub struct PublicClaims<'a> {
80
80
+
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
81
81
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
82
82
+
pub struct PublicClaims<S: BosStr = DefaultStr> {
51
83
/// HTTP method of the request the DPoP proof is bound to (e.g., `"POST"`).
52
52
-
#[serde(borrow)]
53
84
#[serde(skip_serializing_if = "Option::is_none")]
54
54
-
pub htm: Option<CowStr<'a>>,
85
85
+
pub htm: Option<S>,
55
86
/// HTTP target URI of the request the DPoP proof is bound to.
56
87
#[serde(skip_serializing_if = "Option::is_none")]
57
57
-
pub htu: Option<CowStr<'a>>,
88
88
+
pub htu: Option<S>,
58
89
/// Access token hash: base64url-encoded SHA-256 of the access token, binding the proof to a specific token.
59
90
#[serde(skip_serializing_if = "Option::is_none")]
60
60
-
pub ath: Option<CowStr<'a>>,
91
91
+
pub ath: Option<S>,
61
92
/// Server-provided nonce, included to prevent replay attacks when required by the authorization server.
62
93
#[serde(skip_serializing_if = "Option::is_none")]
63
63
-
pub nonce: Option<CowStr<'a>>,
94
94
+
pub nonce: Option<S>,
95
95
+
}
96
96
+
97
97
+
/// Manual `Default` impl to avoid a spurious `S: Default` bound from the derive macro.
98
98
+
///
99
99
+
/// All `S`-typed fields are wrapped in `Option<S>`, which is `Default` regardless of `S`.
100
100
+
impl<S: BosStr> Default for PublicClaims<S> {
101
101
+
fn default() -> Self {
102
102
+
Self {
103
103
+
htm: None,
104
104
+
htu: None,
105
105
+
ath: None,
106
106
+
nonce: None,
107
107
+
}
108
108
+
}
64
109
}
65
110
66
66
-
impl<'a> From<RegisteredClaims<'a>> for Claims<'a> {
67
67
-
fn from(registered: RegisteredClaims<'a>) -> Self {
111
111
+
impl<S: BosStr> From<RegisteredClaims<S>> for Claims<S> {
112
112
+
fn from(registered: RegisteredClaims<S>) -> Self {
68
113
Self {
69
114
registered,
70
115
public: PublicClaims::default(),
···
75
120
/// The `aud` (audience) claim, which may be a single string or a list of strings per RFC 7519.
76
121
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
77
122
#[serde(untagged)]
78
78
-
pub enum RegisteredClaimsAud<'a> {
123
123
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
124
124
+
pub enum RegisteredClaimsAud<S: BosStr = DefaultStr> {
79
125
/// A single audience identifier.
80
80
-
#[serde(borrow)]
81
81
-
Single(CowStr<'a>),
126
126
+
Single(S),
82
127
/// Multiple audience identifiers.
83
83
-
Multiple(Vec<CowStr<'a>>),
128
128
+
Multiple(Vec<S>),
84
129
}
85
130
86
86
-
impl IntoStatic for RegisteredClaims<'_> {
87
87
-
type Output = RegisteredClaims<'static>;
131
131
+
impl<S> IntoStatic for RegisteredClaims<S>
132
132
+
where
133
133
+
S: BosStr + IntoStatic,
134
134
+
S::Output: BosStr,
135
135
+
{
136
136
+
type Output = RegisteredClaims<S::Output>;
88
137
fn into_static(self) -> Self::Output {
89
138
RegisteredClaims {
90
139
iss: self.iss.map(IntoStatic::into_static),
···
98
147
}
99
148
}
100
149
101
101
-
impl IntoStatic for PublicClaims<'_> {
102
102
-
type Output = PublicClaims<'static>;
150
150
+
impl<S> IntoStatic for PublicClaims<S>
151
151
+
where
152
152
+
S: BosStr + IntoStatic,
153
153
+
S::Output: BosStr,
154
154
+
{
155
155
+
type Output = PublicClaims<S::Output>;
103
156
fn into_static(self) -> Self::Output {
104
157
PublicClaims {
105
158
htm: self.htm.map(IntoStatic::into_static),
···
110
163
}
111
164
}
112
165
113
113
-
impl IntoStatic for RegisteredClaimsAud<'_> {
114
114
-
type Output = RegisteredClaimsAud<'static>;
166
166
+
impl<S> IntoStatic for Claims<S>
167
167
+
where
168
168
+
S: BosStr + IntoStatic,
169
169
+
S::Output: BosStr,
170
170
+
{
171
171
+
type Output = Claims<S::Output>;
172
172
+
fn into_static(self) -> Self::Output {
173
173
+
Claims {
174
174
+
registered: self.registered.into_static(),
175
175
+
public: self.public.into_static(),
176
176
+
}
177
177
+
}
178
178
+
}
179
179
+
180
180
+
impl<S> IntoStatic for RegisteredClaimsAud<S>
181
181
+
where
182
182
+
S: BosStr + IntoStatic,
183
183
+
S::Output: BosStr,
184
184
+
{
185
185
+
type Output = RegisteredClaimsAud<S::Output>;
115
186
fn into_static(self) -> Self::Output {
116
187
match self {
117
188
RegisteredClaimsAud::Single(s) => RegisteredClaimsAud::Single(s.into_static()),
···
1
1
use base64::Engine;
2
2
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3
3
-
use jacquard_common::CowStr;
3
3
+
use jacquard_common::bos::BosStr;
4
4
+
use smol_str::SmolStr;
4
5
5
6
use super::{Header, jwt::Claims};
6
7
7
8
/// Builds the base64url-encoded `header.payload` signing input.
8
8
-
fn signing_input(header: &Header, claims: &Claims) -> serde_json::Result<(String, String)> {
9
9
+
fn signing_input<S: BosStr + serde::Serialize>(
10
10
+
header: &Header<S>,
11
11
+
claims: &Claims<&str>,
12
12
+
) -> serde_json::Result<(String, String)> {
9
13
let h = URL_SAFE_NO_PAD.encode(serde_json::to_string(header)?);
10
14
let p = URL_SAFE_NO_PAD.encode(serde_json::to_string(claims)?);
11
15
Ok((h, p))
12
16
}
13
17
14
18
/// Assembles a compact JWS from pre-encoded parts and raw signature bytes.
15
15
-
fn assemble(header: &str, payload: &str, sig: &[u8]) -> CowStr<'static> {
16
16
-
format!("{header}.{payload}.{}", URL_SAFE_NO_PAD.encode(sig)).into()
19
19
+
fn assemble(header: &str, payload: &str, sig: &[u8]) -> SmolStr {
20
20
+
smol_str::format_smolstr!("{header}.{payload}.{}", URL_SAFE_NO_PAD.encode(sig))
17
21
}
18
22
19
23
/// Creates a compact-serialized signed JWT using ES256 (P-256 ECDSA with SHA-256).
20
20
-
pub fn create_signed_jwt_es256(
24
24
+
pub fn create_signed_jwt_es256<S: BosStr + serde::Serialize>(
21
25
key: p256::ecdsa::SigningKey,
22
22
-
header: Header,
23
23
-
claims: Claims,
24
24
-
) -> serde_json::Result<CowStr<'static>> {
26
26
+
header: Header<S>,
27
27
+
claims: Claims<&str>,
28
28
+
) -> serde_json::Result<SmolStr> {
25
29
use p256::ecdsa::signature::Signer;
26
30
let (h, p) = signing_input(&header, &claims)?;
27
31
let sig: p256::ecdsa::Signature = key.sign(format!("{h}.{p}").as_bytes());
···
29
33
}
30
34
31
35
/// Creates a compact-serialized signed JWT using ES384 (P-384 ECDSA with SHA-384).
32
32
-
pub fn create_signed_jwt_es384(
36
36
+
pub fn create_signed_jwt_es384<S: BosStr + serde::Serialize>(
33
37
key: p384::ecdsa::SigningKey,
34
34
-
header: Header,
35
35
-
claims: Claims,
36
36
-
) -> serde_json::Result<CowStr<'static>> {
38
38
+
header: Header<S>,
39
39
+
claims: Claims<&str>,
40
40
+
) -> serde_json::Result<SmolStr> {
37
41
use p384::ecdsa::signature::Signer;
38
42
let (h, p) = signing_input(&header, &claims)?;
39
43
let sig: p384::ecdsa::Signature = key.sign(format!("{h}.{p}").as_bytes());
···
41
45
}
42
46
43
47
/// Creates a compact-serialized signed JWT using ES256K (secp256k1 ECDSA with SHA-256).
44
44
-
pub fn create_signed_jwt_es256k(
48
48
+
pub fn create_signed_jwt_es256k<S: BosStr + serde::Serialize>(
45
49
key: k256::ecdsa::SigningKey,
46
46
-
header: Header,
47
47
-
claims: Claims,
48
48
-
) -> serde_json::Result<CowStr<'static>> {
50
50
+
header: Header<S>,
51
51
+
claims: Claims<&str>,
52
52
+
) -> serde_json::Result<SmolStr> {
49
53
use k256::ecdsa::signature::Signer;
50
54
let (h, p) = signing_input(&header, &claims)?;
51
55
let sig: k256::ecdsa::Signature = key.sign(format!("{h}.{p}").as_bytes());
···
53
57
}
54
58
55
59
/// Creates a compact-serialized signed JWT using EdDSA (Ed25519).
56
56
-
pub fn create_signed_jwt_eddsa(
60
60
+
pub fn create_signed_jwt_eddsa<S: BosStr + serde::Serialize>(
57
61
key: ed25519_dalek::SigningKey,
58
58
-
header: Header,
59
59
-
claims: Claims,
60
60
-
) -> serde_json::Result<CowStr<'static>> {
62
62
+
header: Header<S>,
63
63
+
claims: Claims<&str>,
64
64
+
) -> serde_json::Result<SmolStr> {
61
65
use ed25519_dalek::Signer;
62
66
let (h, p) = signing_input(&header, &claims)?;
63
67
let sig = key.sign(format!("{h}.{p}").as_bytes());
···
1
1
use crate::jose::jws::RegisteredHeader;
2
2
use crate::jose::jwt::Claims;
3
3
use crate::jose::signing;
4
4
-
use jacquard_common::CowStr;
5
4
use jose_jwa::{Algorithm, Signing};
6
5
use jose_jwk::{Class, EcCurves, OkpCurves, crypto};
7
6
use jose_jwk::{Jwk, JwkSet, Key};
7
7
+
use smol_str::{SmolStr, ToSmolStr};
8
8
use std::collections::HashSet;
9
9
use thiserror::Error;
10
10
···
87
87
/// Signs a JWT with the best available key that matches one of the requested algorithms.
88
88
///
89
89
/// Returns [`Error::NotFound`] if no key in the keyset supports any of the given algorithms.
90
90
-
pub fn create_jwt(&self, algs: &[Signing], claims: Claims) -> Result<CowStr<'static>> {
90
90
+
pub fn create_jwt(&self, algs: &[Signing], claims: Claims<&str>) -> Result<SmolStr> {
91
91
let Some(jwk) = self.find_key(algs, Class::Signing) else {
92
92
return Err(Error::NotFound(algs.to_vec()));
93
93
};
···
116
116
None
117
117
}
118
118
119
119
-
fn create_jwt_with_key(&self, key: &Jwk, claims: Claims) -> Result<CowStr<'static>> {
120
120
-
let kid = key.prm.kid.clone().unwrap();
119
119
+
fn create_jwt_with_key(&self, key: &Jwk, claims: Claims<&str>) -> Result<SmolStr> {
120
120
+
let kid = key.prm.kid.as_ref().unwrap().to_smolstr();
121
121
match &key.key {
122
122
Key::Ec(ec) => {
123
123
let d = ec.d.as_ref().ok_or(Error::MissingPrivateKey)?;
···
127
127
let signing_key = p256::ecdsa::SigningKey::from_bytes(d_bytes.into())
128
128
.map_err(|e| Error::InvalidKey(e.to_string()))?;
129
129
let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::Es256));
130
130
-
header.kid = Some(kid.into());
130
130
+
header.kid = Some(kid);
131
131
Ok(signing::create_signed_jwt_es256(
132
132
signing_key,
133
133
header.into(),
···
137
137
EcCurves::P384 => {
138
138
let signing_key = p384::ecdsa::SigningKey::from_bytes(d_bytes.into())
139
139
.map_err(|e| Error::InvalidKey(e.to_string()))?;
140
140
-
let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::Es384));
141
141
-
header.kid = Some(kid.into());
140
140
+
let mut header: RegisteredHeader<&str> =
141
141
+
RegisteredHeader::from(Algorithm::Signing(Signing::Es384));
142
142
+
header.kid = Some(kid.as_str());
142
143
Ok(signing::create_signed_jwt_es384(
143
144
signing_key,
144
145
header.into(),
···
148
149
EcCurves::P256K => {
149
150
let signing_key = k256::ecdsa::SigningKey::from_bytes(d_bytes.into())
150
151
.map_err(|e| Error::InvalidKey(e.to_string()))?;
151
151
-
let mut header =
152
152
+
let mut header: RegisteredHeader<&str> =
152
153
RegisteredHeader::from(Algorithm::Signing(Signing::Es256K));
153
153
-
header.kid = Some(kid.into());
154
154
+
header.kid = Some(kid.as_str());
154
155
Ok(signing::create_signed_jwt_es256k(
155
156
signing_key,
156
157
header.into(),
···
166
167
let d_bytes: &[u8] = d.as_ref();
167
168
let signing_key = ed25519_dalek::SigningKey::try_from(d_bytes)
168
169
.map_err(|e| Error::InvalidKey(e.to_string()))?;
169
169
-
let mut header = RegisteredHeader::from(Algorithm::Signing(Signing::EdDsa));
170
170
-
header.kid = Some(kid.into());
170
170
+
let mut header: RegisteredHeader<&str> =
171
171
+
RegisteredHeader::from(Algorithm::Signing(Signing::EdDsa));
172
172
+
header.kid = Some(kid.as_str());
171
173
Ok(signing::create_signed_jwt_eddsa(
172
174
signing_key,
173
175
header.into(),
···
53
53
resolver::OAuthResolver,
54
54
types::{AuthorizeOptions, CallbackParams},
55
55
};
56
56
+
use jacquard_common::IntoStatic;
56
57
use jacquard_common::deps::fluent_uri::Uri;
57
57
-
use jacquard_common::{IntoStatic, cowstr::ToCowStr};
58
58
use rouille::Server;
59
59
+
use smol_str::{SmolStr, ToSmolStr};
59
60
use std::net::SocketAddr;
60
61
use tokio::sync::mpsc;
61
62
···
115
116
let code = request.get_param("code").unwrap();
116
117
let iss = request.get_param("iss").unwrap();
117
118
let callback_params = CallbackParams {
118
118
-
state: Some(state.to_cowstr().into_static()),
119
119
-
code: code.to_cowstr().into_static(),
120
120
-
iss: Some(iss.to_cowstr().into_static()),
119
119
+
state: Some(state.to_smolstr()),
120
120
+
code: code.to_smolstr(),
121
121
+
iss: Some(iss.to_smolstr()),
121
122
};
122
123
tx.try_send(callback_params).unwrap();
123
124
rouille::Response::text("Logged in!")
···
131
132
#[allow(dead_code)]
132
133
server_handle: std::thread::JoinHandle<()>,
133
134
server_stop: std::sync::mpsc::Sender<()>,
134
134
-
callback_rx: mpsc::Receiver<CallbackParams<'static>>,
135
135
+
callback_rx: mpsc::Receiver<CallbackParams>,
135
136
}
136
137
137
138
/// One-shot OAuth callback server.
···
214
215
pub async fn login_with_local_server(
215
216
&self,
216
217
input: impl AsRef<str>,
217
217
-
opts: AuthorizeOptions<'_>,
218
218
+
opts: AuthorizeOptions<SmolStr>,
218
219
cfg: LoopbackConfig,
219
220
) -> crate::error::Result<super::client::OAuthSession<T, S>> {
220
221
let port = match cfg.port {
···
252
253
pub fn build_localhost_client_data(
253
254
&self,
254
255
cfg: &LoopbackConfig,
255
255
-
opts: &AuthorizeOptions<'_>,
256
256
+
opts: &AuthorizeOptions<SmolStr>,
256
257
local_addr: SocketAddr,
257
257
-
) -> crate::session::ClientData<'static> {
258
258
+
) -> crate::session::ClientData<SmolStr> {
258
259
let redirect_uri = format!("http://{}:{}/oauth/callback", cfg.host, local_addr.port(),);
259
260
let redirect = Uri::parse(redirect_uri).unwrap();
260
261
261
262
let scopes = if opts.scopes.is_empty() {
262
262
-
Some(self.registry.client_data.config.scopes.clone())
263
263
+
Some(
264
264
+
self.registry
265
265
+
.client_data
266
266
+
.config
267
267
+
.scopes
268
268
+
.iter()
269
269
+
.cloned()
270
270
+
.collect(),
271
271
+
)
263
272
} else {
264
264
-
Some(opts.scopes.clone().into_static())
273
273
+
Some(opts.scopes.clone())
265
274
};
266
275
267
276
crate::session::ClientData {
···
1
1
+
use std::str::FromStr;
2
2
+
1
3
use chrono::{TimeDelta, Utc};
2
4
use http::{Method, Request, StatusCode};
3
5
use jacquard_common::{
4
6
CowStr, IntoStatic,
5
5
-
cowstr::ToCowStr,
7
7
+
bos::{BosStr, DefaultStr},
6
8
http_client::HttpClient,
7
9
session::SessionStoreError,
8
10
types::{
···
34
36
OAuthTokenResponse, ParParameters, RefreshRequestParameters, RevocationRequestParameters,
35
37
TokenGrantType, TokenRequestParameters, TokenSet,
36
38
},
37
37
-
utils::{compare_algos, generate_dpop_key, generate_nonce, generate_pkce},
39
39
+
utils::{generate_dpop_key, generate_nonce, generate_pkce},
38
40
};
39
41
40
42
// https://datatracker.ietf.org/doc/html/rfc7523#section-2.2
···
428
430
#[allow(dead_code)]
429
431
pub enum OAuthRequest<'a> {
430
432
/// Standard authorization-code token exchange.
431
431
-
Token(TokenRequestParameters<'a>),
433
433
+
Token(TokenRequestParameters<&'a str>),
432
434
/// Refresh-token grant to obtain a fresh access token.
433
433
-
Refresh(RefreshRequestParameters<'a>),
435
435
+
Refresh(RefreshRequestParameters<&'a str>),
434
436
/// Token revocation request (RFC 7009).
435
435
-
Revocation(RevocationRequestParameters<'a>),
437
437
+
Revocation(RevocationRequestParameters<&'a str>),
436
438
/// Token introspection request (RFC 7662).
437
439
Introspection,
438
440
/// Pushed authorization request (RFC 9126) for pre-registering auth parameters.
439
439
-
PushedAuthorizationRequest(ParParameters<'a>),
441
441
+
PushedAuthorizationRequest(ParParameters<&'a str>),
440
442
}
441
443
442
444
impl OAuthRequest<'_> {
443
445
/// Return a human-readable name for this request variant, used in error messages.
444
444
-
pub fn name(&self) -> CowStr<'static> {
445
445
-
CowStr::new_static(match self {
446
446
+
pub fn name(&self) -> &'static str {
447
447
+
match self {
446
448
Self::Token(_) => "token",
447
449
Self::Refresh(_) => "refresh",
448
450
Self::Revocation(_) => "revocation",
449
451
Self::Introspection => "introspection",
450
452
Self::PushedAuthorizationRequest(_) => "pushed_authorization_request",
451
451
-
})
453
453
+
}
452
454
}
453
455
/// Returns the HTTP status code that a successful response to this request should carry.
454
456
pub fn expected_status(&self) -> StatusCode {
···
476
478
client_assertion_type: Option<CowStr<'a>>,
477
479
/// A JWT signed with the client's private key, proving client identity to the server.
478
480
#[serde(skip_serializing_if = "Option::is_none")]
479
479
-
client_assertion: Option<CowStr<'a>>,
481
481
+
client_assertion: Option<SmolStr>,
480
482
/// The grant-specific parameters (token request, refresh, PAR, etc.) flattened into the body.
481
483
#[serde(flatten)]
482
484
parameters: T,
···
488
490
/// and the optional signing keyset into a single value that is passed to helper functions such
489
491
/// as [`par`], [`exchange_code`], [`refresh`], and [`revoke`].
490
492
#[derive(Debug, Clone)]
491
491
-
pub struct OAuthMetadata {
493
493
+
pub struct OAuthMetadata<S: BosStr = DefaultStr> {
492
494
/// Metadata fetched from the authorization server's `/.well-known/oauth-authorization-server` document.
493
493
-
pub server_metadata: OAuthAuthorizationServerMetadata<'static>,
495
495
+
pub server_metadata: OAuthAuthorizationServerMetadata,
494
496
/// This client's registered metadata, derived from [`crate::atproto::AtprotoClientMetadata`].
495
495
-
pub client_metadata: OAuthClientMetadata<'static>,
497
497
+
pub client_metadata: OAuthClientMetadata<S>,
496
498
/// Optional signing keyset; required for `private_key_jwt` client authentication.
497
499
pub keyset: Option<Keyset>,
498
500
}
499
501
500
500
-
impl OAuthMetadata {
502
502
+
impl<S: BosStr> OAuthMetadata<S> {
501
503
/// Fetch server metadata and assemble an `OAuthMetadata` from an active session context.
502
504
///
503
505
/// Contacts the authorization server recorded in `session_data` to retrieve its current
504
506
/// metadata, then combines it with the client configuration. This is the preferred way to
505
507
/// build an `OAuthMetadata` during token refresh or revocation.
506
506
-
pub async fn new<'r, T: HttpClient + OAuthResolver + Send + Sync>(
508
508
+
pub async fn new<T: HttpClient + OAuthResolver + Send + Sync>(
507
509
client: &T,
508
508
-
ClientData { keyset, config }: &ClientData<'r>,
509
509
-
session_data: &ClientSessionData<'r>,
510
510
-
) -> Result<Self> {
510
510
+
ClientData { keyset, config }: &ClientData<S>,
511
511
+
session_data: &ClientSessionData,
512
512
+
) -> Result<Self>
513
513
+
where
514
514
+
S: Clone + FromStr + Ord,
515
515
+
<S as FromStr>::Err: core::fmt::Debug,
516
516
+
{
511
517
Ok(OAuthMetadata {
512
518
server_metadata: client
513
513
-
.get_authorization_server_metadata(&session_data.authserver_url)
519
519
+
.get_authorization_server_metadata(session_data.authserver_url.as_ref())
514
520
.await?,
515
515
-
client_metadata: atproto_client_metadata(config.clone(), &keyset)
516
516
-
.unwrap()
517
517
-
.into_static(),
521
521
+
client_metadata: atproto_client_metadata(&config, &keyset)?,
518
522
keyset: keyset.clone(),
519
523
})
520
524
}
···
527
531
/// persisted (e.g., in the auth store) so it can be retrieved and verified during
528
532
/// [`crate::client::OAuthClient::callback`].
529
533
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all, fields(login_hint = login_hint.as_ref().map(|h| h.as_ref()))))]
530
530
-
pub async fn par<'r, T: OAuthResolver + DpopExt + Send + Sync + 'static>(
534
534
+
pub async fn par<
535
535
+
S: BosStr + Clone + Send + Sync,
536
536
+
T: OAuthResolver + DpopExt + Send + Sync + 'static,
537
537
+
>(
531
538
client: &T,
532
532
-
login_hint: Option<CowStr<'r>>,
539
539
+
login_hint: Option<S>,
533
540
prompt: Option<AuthorizeOptionPrompt>,
534
534
-
metadata: &OAuthMetadata,
535
535
-
state: Option<CowStr<'r>>,
536
536
-
) -> crate::request::Result<AuthRequestData<'r>> {
537
537
-
let state = if let Some(state) = state {
538
538
-
state
539
539
-
} else {
540
540
-
generate_nonce()
541
541
-
};
541
541
+
metadata: &mut OAuthMetadata<S>,
542
542
+
state: Option<SmolStr>,
543
543
+
) -> crate::request::Result<AuthRequestData> {
544
544
+
let state = state.unwrap_or_else(generate_nonce);
542
545
let (code_challenge, verifier) = generate_pkce();
543
546
544
544
-
let Some(dpop_key) = generate_dpop_key(&metadata.server_metadata) else {
547
547
+
let Some(dpop_key) = generate_dpop_key(&mut metadata.server_metadata) else {
545
548
return Err(RequestError::token_verification());
546
549
};
547
550
let mut dpop_data = DpopReqData {
548
551
dpop_key,
549
552
dpop_authserver_nonce: None,
550
553
};
551
551
-
let parameters = ParParameters {
554
554
+
let parameters: ParParameters<&str> = ParParameters {
552
555
response_type: AuthorizationResponseType::Code,
553
553
-
redirect_uri: metadata.client_metadata.redirect_uris[0].to_cowstr(),
554
554
-
state: state.clone(),
555
555
-
scope: metadata.client_metadata.scope.clone(),
556
556
+
redirect_uri: metadata.client_metadata.redirect_uris[0].as_ref(),
557
557
+
state: state.as_ref(),
558
558
+
scope: metadata.client_metadata.scope.as_ref().map(|s| s.as_ref()),
556
559
response_mode: None,
557
557
-
code_challenge,
560
560
+
code_challenge: code_challenge.as_str(),
558
561
code_challenge_method: AuthorizationCodeChallengeMethod::S256,
559
559
-
login_hint: login_hint,
560
560
-
prompt: prompt.map(CowStr::from),
562
562
+
login_hint: login_hint.as_ref().map(|h| h.as_ref()),
563
563
+
prompt: prompt.map(|p| p.into()),
561
564
};
562
565
563
566
if metadata
···
565
568
.pushed_authorization_request_endpoint
566
569
.is_some()
567
570
{
568
568
-
let par_response = oauth_request::<OAuthParResponse, T, DpopReqData>(
571
571
+
let par_response = oauth_request::<OAuthParResponse, T, DpopReqData, _>(
569
572
&client,
570
573
&mut dpop_data,
571
574
OAuthRequest::PushedAuthorizationRequest(parameters),
···
574
577
.await?;
575
578
576
579
let scopes = if let Some(scope) = &metadata.client_metadata.scope {
577
577
-
Scope::parse_multiple_reduced(&scope)
580
580
+
Scope::<SmolStr>::parse_multiple_reduced(scope.as_ref())
578
581
.expect("Failed to parse scopes")
579
582
.into_static()
580
583
} else {
581
584
vec![]
582
585
};
583
583
-
let auth_req_data = AuthRequestData {
584
584
-
state,
585
585
-
authserver_url: metadata.server_metadata.issuer.clone(),
586
586
+
let auth_req_data: AuthRequestData = AuthRequestData {
587
587
+
state: state.into(),
588
588
+
authserver_url: metadata.server_metadata.issuer.to_smolstr(),
586
589
account_did: None,
587
590
scopes,
588
588
-
request_uri: par_response.request_uri.to_cowstr().into_static(),
589
589
-
authserver_token_endpoint: metadata.server_metadata.token_endpoint.clone(),
591
591
+
request_uri: par_response.request_uri.clone(),
592
592
+
authserver_token_endpoint: metadata.server_metadata.token_endpoint.to_smolstr(),
590
593
authserver_revocation_endpoint: metadata.server_metadata.revocation_endpoint.clone(),
591
591
-
pkce_verifier: verifier,
594
594
+
pkce_verifier: verifier.into(),
592
595
dpop_data,
593
596
};
594
597
···
606
609
607
610
/// Exchange a refresh token for a fresh token set and update the session data in place.
608
611
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all, fields(did = %session_data.account_did)))]
609
609
-
pub async fn refresh<'r, T>(
612
612
+
pub async fn refresh<S, T>(
610
613
client: &T,
611
611
-
mut session_data: ClientSessionData<'r>,
612
612
-
metadata: &OAuthMetadata,
613
613
-
) -> Result<ClientSessionData<'r>>
614
614
+
mut session_data: ClientSessionData,
615
615
+
metadata: &OAuthMetadata<S>,
616
616
+
) -> Result<ClientSessionData>
614
617
where
618
618
+
S: BosStr + FromStr,
615
619
T: OAuthResolver + DpopExt + Send + Sync + 'static,
616
620
{
617
621
let Some(refresh_token) = session_data.token_set.refresh_token.as_ref() else {
···
631
635
.await?;
632
636
let iss = metadata.server_metadata.issuer.clone();
633
637
634
634
-
let response = oauth_request::<OAuthTokenResponse, T, DpopClientData>(
638
638
+
let response = oauth_request::<OAuthTokenResponse, T, DpopClientData, _>(
635
639
client,
636
640
&mut session_data.dpop_data,
637
641
OAuthRequest::Refresh(RefreshRequestParameters {
638
642
grant_type: TokenGrantType::RefreshToken,
639
639
-
refresh_token: refresh_token.clone(),
643
643
+
refresh_token: refresh_token.as_ref(),
640
644
scope: None,
641
645
}),
642
646
metadata,
···
650
654
.map(Datetime::new)
651
655
});
652
656
653
653
-
session_data.update_with_tokens(TokenSet {
657
657
+
session_data.update_with_tokens(&TokenSet {
654
658
iss,
655
659
sub: session_data.token_set.sub.clone(),
656
656
-
aud: CowStr::Owned(aud.to_smolstr()),
657
657
-
scope: response.scope.map(CowStr::Owned),
658
658
-
access_token: CowStr::Owned(response.access_token),
659
659
-
refresh_token: response.refresh_token.map(CowStr::Owned),
660
660
+
aud: SmolStr::from(aud.as_str()),
661
661
+
scope: response.scope,
662
662
+
access_token: response.access_token,
663
663
+
refresh_token: response.refresh_token,
660
664
token_type: response.token_type,
661
665
expires_at,
662
666
});
···
671
675
/// function performs that verification as part of the exchange, so callers receive a token
672
676
/// set that is safe to persist.
673
677
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))]
674
674
-
pub async fn exchange_code<'r, T, D>(
678
678
+
pub async fn exchange_code<S, T, D>(
675
679
client: &T,
676
676
-
data_source: &'r mut D,
680
680
+
data_source: &mut D,
677
681
code: &str,
678
682
verifier: &str,
679
679
-
metadata: &OAuthMetadata,
680
680
-
) -> Result<TokenSet<'r>>
683
683
+
metadata: &OAuthMetadata<S>,
684
684
+
) -> Result<TokenSet>
681
685
where
686
686
+
S: BosStr + Send + Sync,
682
687
T: OAuthResolver + DpopExt + Send + Sync + 'static,
683
688
D: DpopDataSource,
684
689
{
685
685
-
let token_response = oauth_request::<OAuthTokenResponse, T, D>(
690
690
+
let token_response = oauth_request::<OAuthTokenResponse, T, D, _>(
686
691
client,
687
692
data_source,
688
693
OAuthRequest::Token(TokenRequestParameters {
689
694
grant_type: TokenGrantType::AuthorizationCode,
690
695
code: code.into(),
691
691
-
redirect_uri: CowStr::Owned(
692
692
-
metadata.client_metadata.redirect_uris[0]
693
693
-
.clone()
694
694
-
.to_smolstr(),
695
695
-
),
696
696
+
redirect_uri: metadata.client_metadata.redirect_uris[0].as_ref(),
696
697
code_verifier: verifier.into(),
697
698
}),
698
699
metadata,
···
720
721
Ok(TokenSet {
721
722
iss,
722
723
sub,
723
723
-
aud: CowStr::Owned(aud.to_smolstr()),
724
724
-
scope: token_response.scope.map(CowStr::Owned),
725
725
-
access_token: CowStr::Owned(token_response.access_token),
726
726
-
refresh_token: token_response.refresh_token.map(CowStr::Owned),
724
724
+
aud: SmolStr::from(aud.as_str()),
725
725
+
scope: token_response.scope,
726
726
+
access_token: token_response.access_token,
727
727
+
refresh_token: token_response.refresh_token,
727
728
token_type: token_response.token_type,
728
729
expires_at,
729
730
})
···
735
736
/// by the server. The caller is responsible for deleting the session from local storage regardless
736
737
/// of whether revocation succeeds.
737
738
#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))]
738
738
-
pub async fn revoke<'r, T, D>(
739
739
+
pub async fn revoke<S: BosStr + Send + Sync, T, D>(
739
740
client: &T,
740
740
-
data_source: &'r mut D,
741
741
+
data_source: &mut D,
741
742
token: &str,
742
742
-
metadata: &OAuthMetadata,
743
743
+
metadata: &OAuthMetadata<S>,
743
744
) -> Result<()>
744
745
where
745
746
T: OAuthResolver + DpopExt + Send + Sync + 'static,
746
747
D: DpopDataSource,
747
748
{
748
748
-
oauth_request::<(), T, D>(
749
749
+
oauth_request::<(), T, D, _>(
749
750
client,
750
751
data_source,
751
752
OAuthRequest::Revocation(RevocationRequestParameters {
···
763
764
/// client authentication, performs the DPoP-wrapped HTTP POST, and deserializes the response
764
765
/// body into `O`. The type parameter `O` is inferred from the call site; use `()` for requests
765
766
/// where the response body is empty (e.g., revocation).
766
766
-
pub async fn oauth_request<'de: 'r, 'r, O, T, D>(
767
767
+
pub async fn oauth_request<'r, O, T, D, S: BosStr>(
767
768
client: &T,
768
768
-
data_source: &'r mut D,
769
769
+
data_source: &mut D,
769
770
request: OAuthRequest<'r>,
770
770
-
metadata: &OAuthMetadata,
771
771
+
metadata: &OAuthMetadata<S>,
771
772
) -> Result<O>
772
773
where
773
774
T: OAuthResolver + DpopExt + Send + Sync + 'static,
···
792
793
_ => unimplemented!(),
793
794
};
794
795
let req = Request::builder()
795
795
-
.uri(url.to_string())
796
796
+
.uri(url)
796
797
.method(Method::POST)
797
798
.header("Content-Type", "application/x-www-form-urlencoded")
798
799
.body(body.into_bytes())?;
···
817
818
}
818
819
819
820
#[inline]
820
820
-
fn endpoint_for_req<'a, 'r>(
821
821
-
server_metadata: &'r OAuthAuthorizationServerMetadata<'a>,
821
821
+
fn endpoint_for_req<'r, S: BosStr>(
822
822
+
server_metadata: &'r OAuthAuthorizationServerMetadata<S>,
822
823
request: &'r OAuthRequest,
823
823
-
) -> Option<&'r CowStr<'a>> {
824
824
+
) -> Option<&'r str> {
824
825
match request {
825
825
-
OAuthRequest::Token(_) | OAuthRequest::Refresh(_) => Some(&server_metadata.token_endpoint),
826
826
-
OAuthRequest::Revocation(_) => server_metadata.revocation_endpoint.as_ref(),
827
827
-
OAuthRequest::Introspection => server_metadata.introspection_endpoint.as_ref(),
826
826
+
OAuthRequest::Token(_) | OAuthRequest::Refresh(_) => {
827
827
+
Some(server_metadata.token_endpoint.as_ref())
828
828
+
}
829
829
+
OAuthRequest::Revocation(_) => server_metadata
830
830
+
.revocation_endpoint
831
831
+
.as_ref()
832
832
+
.map(AsRef::as_ref),
833
833
+
OAuthRequest::Introspection => server_metadata
834
834
+
.introspection_endpoint
835
835
+
.as_ref()
836
836
+
.map(AsRef::as_ref),
828
837
OAuthRequest::PushedAuthorizationRequest(_) => server_metadata
829
838
.pushed_authorization_request_endpoint
830
830
-
.as_ref(),
839
839
+
.as_ref()
840
840
+
.map(AsRef::as_ref),
831
841
}
832
842
}
833
843
···
856
866
/// Either absent (for `none` auth) or `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
857
867
assertion_type: Option<CowStr<'a>>,
858
868
/// A signed JWT proving client identity; present only for `private_key_jwt` auth.
859
859
-
assertion: Option<CowStr<'a>>,
869
869
+
assertion: Option<SmolStr>,
860
870
}
861
871
862
872
impl<'s> ClientAuth<'s> {
···
870
880
}
871
881
}
872
882
873
873
-
fn build_auth<'a>(
883
883
+
fn build_auth<'a, S: BosStr>(
874
884
keyset: Option<&Keyset>,
875
875
-
server_metadata: &OAuthAuthorizationServerMetadata<'a>,
876
876
-
client_metadata: &OAuthClientMetadata<'a>,
885
885
+
server_metadata: &'a OAuthAuthorizationServerMetadata,
886
886
+
client_metadata: &'a OAuthClientMetadata<S>,
877
887
) -> Result<ClientAuth<'a>> {
878
888
let method_supported = server_metadata
879
889
.token_endpoint_auth_methods_supported
880
890
.as_ref();
881
891
882
882
-
let client_id = client_metadata.client_id.to_cowstr().into_static();
892
892
+
let client_id = CowStr::Borrowed(client_metadata.client_id.as_ref());
883
893
if let Some(method) = client_metadata.token_endpoint_auth_method.as_ref() {
884
884
-
match (*method).as_ref() {
894
894
+
match method.as_ref() {
885
895
"private_key_jwt"
886
896
if method_supported
887
897
.as_ref()
888
888
-
.is_some_and(|v| v.contains(&CowStr::new_static("private_key_jwt"))) =>
898
898
+
.is_some_and(|v| v.iter().any(|s| s.as_str() == "private_key_jwt")) =>
889
899
{
890
900
if let Some(keyset) = &keyset {
891
891
-
let mut alg_strs = server_metadata
901
901
+
let mut alg_strs: Vec<&str> = server_metadata
892
902
.token_endpoint_auth_signing_alg_values_supported
893
893
-
.clone()
894
894
-
.unwrap_or(vec![FALLBACK_ALG.into()]);
895
895
-
alg_strs.sort_by(compare_algos);
903
903
+
.as_ref()
904
904
+
.map(|v| v.iter().map(|s| s.as_ref()).collect())
905
905
+
.unwrap_or_default();
906
906
+
if alg_strs.is_empty() {
907
907
+
alg_strs.push(FALLBACK_ALG);
908
908
+
}
896
909
let algs: Vec<Signing> = alg_strs
897
910
.iter()
898
911
.filter_map(|s| crate::keyset::parse_signing_alg(s))
899
912
.collect();
900
913
let iat = Utc::now().timestamp();
914
914
+
let client_id_str: &str = client_metadata.client_id.as_ref();
915
915
+
let issuer_str: &str = server_metadata.issuer.as_ref();
901
916
return Ok(ClientAuth {
902
917
client_id: client_id.clone(),
903
918
assertion_type: Some(CowStr::new_static(CLIENT_ASSERTION_TYPE_JWT_BEARER)),
···
906
921
&algs,
907
922
// https://datatracker.ietf.org/doc/html/rfc7523#section-3
908
923
RegisteredClaims {
909
909
-
iss: Some(client_id.clone()),
910
910
-
sub: Some(client_id),
911
911
-
aud: Some(RegisteredClaimsAud::Single(
912
912
-
server_metadata.issuer.clone(),
913
913
-
)),
924
924
+
iss: Some(client_id_str),
925
925
+
sub: Some(client_id_str),
926
926
+
aud: Some(RegisteredClaimsAud::Single(issuer_str)),
914
927
exp: Some(iat + 60),
915
928
// "iat" is required and **MUST** be less than one minute
916
929
// https://datatracker.ietf.org/doc/html/rfc9101
···
928
941
"none"
929
942
if method_supported
930
943
.as_ref()
931
931
-
.is_some_and(|v| v.contains(&CowStr::new_static("none"))) =>
944
944
+
.is_some_and(|v| v.iter().any(|s| s.as_str() == "none")) =>
932
945
{
933
946
return Ok(ClientAuth::new_id(client_id));
934
947
}
···
945
958
use crate::types::{OAuthAuthorizationServerMetadata, OAuthClientMetadata};
946
959
use bytes::Bytes;
947
960
use http::{Response as HttpResponse, StatusCode};
948
948
-
use jacquard_common::{deps::fluent_uri::Uri, http_client::HttpClient, types::string::Did};
961
961
+
use jacquard_common::{
962
962
+
bos::BosStr, deps::fluent_uri::Uri, http_client::HttpClient, types::string::Did,
963
963
+
};
949
964
use jacquard_identity::resolver::IdentityResolver;
965
965
+
use smol_str::SmolStr;
950
966
use std::sync::Arc;
951
967
use tokio::sync::Mutex;
952
968
···
976
992
LazyLock::new(|| jacquard_identity::resolver::ResolverOptions::default());
977
993
&OPTS
978
994
}
979
979
-
async fn resolve_handle(
995
995
+
async fn resolve_handle<S: BosStr + Sync>(
980
996
&self,
981
981
-
_handle: &jacquard_common::types::string::Handle<'_>,
982
982
-
) -> std::result::Result<Did<'static>, jacquard_identity::resolver::IdentityError> {
997
997
+
_handle: &jacquard_common::types::string::Handle<S>,
998
998
+
) -> std::result::Result<Did, jacquard_identity::resolver::IdentityError> {
983
999
Ok(Did::new_static("did:plc:alice").unwrap())
984
1000
}
985
985
-
async fn resolve_did_doc(
1001
1001
+
async fn resolve_did_doc<S: BosStr + Sync>(
986
1002
&self,
987
987
-
_did: &Did<'_>,
1003
1003
+
_did: &Did<S>,
988
1004
) -> std::result::Result<
989
1005
jacquard_identity::resolver::DidDocResponse,
990
1006
jacquard_identity::resolver::IdentityError,
···
1012
1028
1013
1029
fn base_metadata() -> OAuthMetadata {
1014
1030
let mut server = OAuthAuthorizationServerMetadata::default();
1015
1015
-
server.issuer = CowStr::from("https://issuer");
1016
1016
-
server.authorization_endpoint = CowStr::from("https://issuer/authorize");
1017
1017
-
server.token_endpoint = CowStr::from("https://issuer/token");
1018
1018
-
server.token_endpoint_auth_methods_supported = Some(vec![CowStr::from("none")]);
1031
1031
+
server.issuer = SmolStr::new_static("https://issuer");
1032
1032
+
server.authorization_endpoint = SmolStr::new_static("https://issuer/authorize");
1033
1033
+
server.token_endpoint = SmolStr::new_static("https://issuer/token");
1034
1034
+
server.token_endpoint_auth_methods_supported = Some(vec![SmolStr::new_static("none")]);
1019
1035
OAuthMetadata {
1020
1036
server_metadata: server,
1021
1037
client_metadata: OAuthClientMetadata {
1022
1022
-
client_id: CowStr::new_static("https://client"),
1038
1038
+
client_id: SmolStr::new_static("https://client"),
1023
1039
client_uri: None,
1024
1024
-
redirect_uris: vec![CowStr::new_static("https://client/cb")],
1025
1025
-
scope: Some(CowStr::from("atproto")),
1040
1040
+
redirect_uris: vec![SmolStr::new_static("https://client/cb")],
1041
1041
+
scope: Some(SmolStr::new_static("atproto")),
1026
1042
grant_types: None,
1027
1027
-
response_types: vec![CowStr::new_static("code")],
1028
1028
-
application_type: Some(CowStr::new_static("web")),
1029
1029
-
token_endpoint_auth_method: Some(CowStr::from("none")),
1043
1043
+
response_types: vec![SmolStr::new_static("code")],
1044
1044
+
application_type: Some(SmolStr::new_static("web")),
1045
1045
+
token_endpoint_auth_method: Some(SmolStr::new_static("none")),
1030
1046
dpop_bound_access_tokens: None,
1031
1047
jwks_uri: None,
1032
1048
jwks: None,
···
1046
1062
meta.server_metadata.require_pushed_authorization_requests = Some(true);
1047
1063
meta.server_metadata.pushed_authorization_request_endpoint = None;
1048
1064
// require_pushed_authorization_requests is true and no endpoint
1049
1049
-
let err = super::par(&MockClient::default(), None, None, &meta, None)
1065
1065
+
let err = super::par(&MockClient::default(), None, None, &mut meta, None)
1050
1066
.await
1051
1067
.unwrap_err();
1052
1068
assert!(
···
1060
1076
let meta = base_metadata();
1061
1077
let session = ClientSessionData {
1062
1078
account_did: Did::new_static("did:plc:alice").unwrap(),
1063
1063
-
session_id: CowStr::from("state"),
1079
1079
+
session_id: SmolStr::new_static("state"),
1064
1080
host_url: Uri::parse("https://pds").expect("valid").to_owned(),
1065
1065
-
authserver_url: CowStr::new_static("https://issuer"),
1066
1066
-
authserver_token_endpoint: CowStr::from("https://issuer/token"),
1081
1081
+
authserver_url: SmolStr::new_static("https://issuer"),
1082
1082
+
authserver_token_endpoint: SmolStr::new_static("https://issuer/token"),
1067
1083
authserver_revocation_endpoint: None,
1068
1084
scopes: vec![],
1069
1085
dpop_data: DpopClientData {
1070
1070
-
dpop_key: crate::utils::generate_key(&[CowStr::from("ES256")]).unwrap(),
1071
1071
-
dpop_authserver_nonce: CowStr::from(""),
1072
1072
-
dpop_host_nonce: CowStr::from(""),
1086
1086
+
dpop_key: crate::utils::generate_key(&[SmolStr::new_static("ES256")]).unwrap(),
1087
1087
+
dpop_authserver_nonce: SmolStr::default(),
1088
1088
+
dpop_host_nonce: SmolStr::default(),
1073
1089
},
1074
1090
token_set: crate::types::TokenSet {
1075
1075
-
iss: CowStr::from("https://issuer"),
1091
1091
+
iss: SmolStr::new_static("https://issuer"),
1076
1092
sub: Did::new_static("did:plc:alice").unwrap(),
1077
1077
-
aud: CowStr::from("https://pds"),
1093
1093
+
aud: SmolStr::new_static("https://pds"),
1078
1094
scope: None,
1079
1095
refresh_token: None,
1080
1080
-
access_token: CowStr::from("abc"),
1096
1096
+
access_token: SmolStr::new_static("abc"),
1081
1097
token_type: crate::types::OAuthTokenType::DPoP,
1082
1098
expires_at: None,
1083
1099
},
···
1105
1121
);
1106
1122
let meta = base_metadata();
1107
1123
let mut dpop = DpopReqData {
1108
1108
-
dpop_key: crate::utils::generate_key(&[CowStr::from("ES256")]).unwrap(),
1124
1124
+
dpop_key: crate::utils::generate_key(&[SmolStr::new_static("ES256")]).unwrap(),
1109
1125
dpop_authserver_nonce: None,
1110
1126
};
1111
1127
let err = super::exchange_code(&client, &mut dpop, "abc", "verifier", &meta)
···
3
3
4
4
use crate::types::{OAuthAuthorizationServerMetadata, OAuthProtectedResourceMetadata};
5
5
use http::{Request, StatusCode};
6
6
-
use jacquard_common::CowStr;
6
6
+
#[cfg(not(target_arch = "wasm32"))]
7
7
+
use jacquard_common::BosStr;
7
8
use jacquard_common::IntoStatic;
8
8
-
#[allow(unused_imports)]
9
9
-
use jacquard_common::cowstr::ToCowStr;
10
9
use jacquard_common::deps::fluent_uri::Uri;
11
10
use jacquard_common::types::did_doc::DidDocument;
12
11
use jacquard_common::types::ident::AtIdentifier;
···
96
95
code(jacquard_oauth::resolver::unsupported_did_method),
97
96
help("supported DID methods: did:web, did:plc")
98
97
)]
99
99
-
UnsupportedDidMethod(Did<'static>),
98
98
+
UnsupportedDidMethod(Did),
100
99
101
100
/// HTTP transport error
102
101
#[error("transport error")]
···
245
244
}
246
245
247
246
/// Create an unsupported DID method error
248
248
-
pub fn unsupported_did_method(did: Did<'static>) -> Self {
247
247
+
pub fn unsupported_did_method(did: Did) -> Self {
249
248
Self::new(ResolverErrorKind::UnsupportedDidMethod(did), None)
250
249
}
251
250
···
319
318
// }
320
319
321
320
#[cfg(not(target_arch = "wasm32"))]
322
322
-
async fn verify_issuer_impl<T: OAuthResolver + Sync + ?Sized>(
321
321
+
async fn verify_issuer_impl<S: BosStr, T: OAuthResolver + Sync + ?Sized>(
323
322
resolver: &T,
324
324
-
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
325
325
-
sub: &Did<'_>,
323
323
+
server_metadata: &OAuthAuthorizationServerMetadata,
324
324
+
sub: &Did<S>,
326
325
) -> Result<Uri<String>> {
327
326
let (metadata, identity) = resolver.resolve_from_identity(sub.as_str()).await?;
328
328
-
if metadata.issuer != server_metadata.issuer {
327
327
+
if metadata.issuer.as_str() != server_metadata.issuer.as_str() {
329
328
return Err(ResolverError::authorization_server_metadata(
330
329
"issuer mismatch",
331
330
));
332
331
}
333
332
Ok(identity
334
333
.pds_endpoint()
335
335
-
.ok_or_else(|| ResolverError::did_document(smol_str::format_smolstr!("{:?}", identity)))?)
334
334
+
.ok_or_else(|| ResolverError::did_document(smol_str::format_smolstr!("{:?}", identity)))?
335
335
+
.to_owned())
336
336
}
337
337
338
338
#[cfg(target_arch = "wasm32")]
339
339
-
async fn verify_issuer_impl<T: OAuthResolver + ?Sized>(
339
339
+
async fn verify_issuer_impl<S: BosStr, T: OAuthResolver + ?Sized>(
340
340
resolver: &T,
341
341
-
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
342
342
-
sub: &Did<'_>,
341
341
+
server_metadata: &OAuthAuthorizationServerMetadata,
342
342
+
sub: &Did<S>,
343
343
) -> Result<Uri<String>> {
344
344
let (metadata, identity) = resolver.resolve_from_identity(sub.as_str()).await?;
345
345
-
if metadata.issuer != server_metadata.issuer {
345
345
+
if metadata.issuer.as_str() != server_metadata.issuer.as_str() {
346
346
return Err(ResolverError::authorization_server_metadata(
347
347
"issuer mismatch",
348
348
));
349
349
}
350
350
Ok(identity
351
351
.pds_endpoint()
352
352
-
.ok_or_else(|| ResolverError::did_document(smol_str::format_smolstr!("{:?}", identity)))?)
352
352
+
.ok_or_else(|| ResolverError::did_document(smol_str::format_smolstr!("{:?}", identity)))?
353
353
+
.to_owned())
353
354
}
354
355
355
356
#[cfg(not(target_arch = "wasm32"))]
356
357
async fn resolve_oauth_impl<T: OAuthResolver + Sync + ?Sized>(
357
358
resolver: &T,
358
359
input: &str,
359
359
-
) -> Result<(
360
360
-
OAuthAuthorizationServerMetadata<'static>,
361
361
-
Option<DidDocument<'static>>,
362
362
-
)> {
360
360
+
) -> Result<(OAuthAuthorizationServerMetadata, Option<DidDocument>)> {
363
361
// Allow using an entryway, or PDS url, directly as login input (e.g.
364
362
// when the user forgot their handle, or when the handle does not
365
363
// resolve to a DID)
···
370
368
err.with_context("failed to parse service URL")
371
369
})?
372
370
.to_owned();
373
373
-
(
374
374
-
resolver.resolve_from_service(&uri.as_str().into()).await?,
375
375
-
None,
376
376
-
)
371
371
+
(resolver.resolve_from_service(uri.as_str()).await?, None)
377
372
} else {
378
373
let (metadata, identity) = resolver.resolve_from_identity(input).await?;
379
374
(metadata, Some(identity))
···
384
379
async fn resolve_oauth_impl<T: OAuthResolver + ?Sized>(
385
380
resolver: &T,
386
381
input: &str,
387
387
-
) -> Result<(
388
388
-
OAuthAuthorizationServerMetadata<'static>,
389
389
-
Option<DidDocument<'static>>,
390
390
-
)> {
382
382
+
) -> Result<(OAuthAuthorizationServerMetadata, Option<DidDocument>)> {
391
383
// Allow using an entryway, or PDS url, directly as login input (e.g.
392
384
// when the user forgot their handle, or when the handle does not
393
385
// resolve to a DID)
···
398
390
err.with_context("failed to parse service URL")
399
391
})?
400
392
.to_owned();
401
401
-
(
402
402
-
resolver.resolve_from_service(&uri.as_str().into()).await?,
403
403
-
None,
404
404
-
)
393
393
+
(resolver.resolve_from_service(uri.as_str()).await?, None)
405
394
} else {
406
395
let (metadata, identity) = resolver.resolve_from_identity(input).await?;
407
396
(metadata, Some(identity))
···
411
400
#[cfg(not(target_arch = "wasm32"))]
412
401
async fn resolve_from_service_impl<T: OAuthResolver + Sync + ?Sized>(
413
402
resolver: &T,
414
414
-
input: &CowStr<'_>,
415
415
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
403
403
+
input: &str,
404
404
+
) -> Result<OAuthAuthorizationServerMetadata> {
416
405
// Assume first that input is a PDS URL (as required by ATPROTO)
417
406
if let Ok(metadata) = resolver.get_resource_server_metadata(input).await {
418
407
return Ok(metadata);
···
424
413
#[cfg(target_arch = "wasm32")]
425
414
async fn resolve_from_service_impl<T: OAuthResolver + ?Sized>(
426
415
resolver: &T,
427
427
-
input: &CowStr<'_>,
428
428
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
416
416
+
input: &str,
417
417
+
) -> Result<OAuthAuthorizationServerMetadata> {
429
418
// Assume first that input is a PDS URL (as required by ATPROTO)
430
419
if let Ok(metadata) = resolver.get_resource_server_metadata(input).await {
431
420
return Ok(metadata);
···
438
427
async fn resolve_from_identity_impl<T: OAuthResolver + Sync + ?Sized>(
439
428
resolver: &T,
440
429
input: &str,
441
441
-
) -> Result<(
442
442
-
OAuthAuthorizationServerMetadata<'static>,
443
443
-
DidDocument<'static>,
444
444
-
)> {
430
430
+
) -> Result<(OAuthAuthorizationServerMetadata, DidDocument)> {
445
431
let actor = AtIdentifier::new(input)
446
432
.map_err(|e| ResolverError::at_identifier(smol_str::format_smolstr!("{:?}", e)))?;
447
433
let identity = resolver.resolve_ident_owned(&actor).await?;
448
434
if let Some(pds) = &identity.pds_endpoint() {
449
449
-
use jacquard_common::cowstr::ToCowStr;
450
450
-
451
451
-
let metadata = resolver
452
452
-
.get_resource_server_metadata(&pds.to_cowstr())
453
453
-
.await?;
435
435
+
let metadata = resolver.get_resource_server_metadata(pds.as_str()).await?;
454
436
Ok((metadata, identity))
455
437
} else {
456
438
Err(ResolverError::did_document("Did doc lacking pds"))
···
461
443
async fn resolve_from_identity_impl<T: OAuthResolver + ?Sized>(
462
444
resolver: &T,
463
445
input: &str,
464
464
-
) -> Result<(
465
465
-
OAuthAuthorizationServerMetadata<'static>,
466
466
-
DidDocument<'static>,
467
467
-
)> {
446
446
+
) -> Result<(OAuthAuthorizationServerMetadata, DidDocument)> {
468
447
let actor = AtIdentifier::new(input)
469
448
.map_err(|e| ResolverError::at_identifier(smol_str::format_smolstr!("{:?}", e)))?;
470
449
let identity = resolver.resolve_ident_owned(&actor).await?;
471
450
if let Some(pds) = &identity.pds_endpoint() {
472
472
-
let metadata = resolver
473
473
-
.get_resource_server_metadata(&pds.to_cowstr())
474
474
-
.await?;
451
451
+
let metadata = resolver.get_resource_server_metadata(pds.as_str()).await?;
475
452
Ok((metadata, identity))
476
453
} else {
477
454
Err(ResolverError::did_document("Did doc lacking pds"))
···
481
458
#[cfg(not(target_arch = "wasm32"))]
482
459
async fn get_authorization_server_metadata_impl<T: HttpClient + Sync + ?Sized>(
483
460
client: &T,
484
484
-
issuer: &CowStr<'_>,
485
485
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
461
461
+
issuer: &str,
462
462
+
) -> Result<OAuthAuthorizationServerMetadata> {
486
463
let mut md = resolve_authorization_server(client, issuer).await?;
487
487
-
md.issuer = issuer.clone().into_static();
464
464
+
md.issuer = SmolStr::from(issuer);
488
465
Ok(md)
489
466
}
490
467
491
468
#[cfg(target_arch = "wasm32")]
492
469
async fn get_authorization_server_metadata_impl<T: HttpClient + ?Sized>(
493
470
client: &T,
494
494
-
issuer: &CowStr<'_>,
495
495
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
471
471
+
issuer: &str,
472
472
+
) -> Result<OAuthAuthorizationServerMetadata> {
496
473
let mut md = resolve_authorization_server(client, issuer).await?;
497
497
-
md.issuer = issuer.clone().into_static();
474
474
+
md.issuer = SmolStr::from(issuer);
498
475
Ok(md)
499
476
}
500
477
501
478
#[cfg(not(target_arch = "wasm32"))]
502
479
async fn get_resource_server_metadata_impl<T: OAuthResolver + Sync + ?Sized>(
503
480
resolver: &T,
504
504
-
pds: &CowStr<'_>,
505
505
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
481
481
+
pds: &str,
482
482
+
) -> Result<OAuthAuthorizationServerMetadata> {
506
483
let rs_metadata = resolve_protected_resource_info(resolver, pds).await?;
507
484
// ATPROTO requires one, and only one, authorization server entry
508
485
// > That document MUST contain a single item in the authorization_servers array.
···
524
501
));
525
502
}
526
503
};
527
527
-
let as_metadata = resolver.get_authorization_server_metadata(issuer).await?;
504
504
+
let as_metadata = resolver
505
505
+
.get_authorization_server_metadata(issuer.as_ref())
506
506
+
.await?;
528
507
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-08#name-authorization-server-metada
529
508
if let Some(protected_resources) = &as_metadata.protected_resources {
530
509
let resource_url = rs_metadata
531
510
.resource
532
511
.strip_suffix('/')
533
512
.unwrap_or(rs_metadata.resource.as_str());
534
534
-
if !protected_resources.contains(&CowStr::Borrowed(resource_url)) {
513
513
+
if !protected_resources
514
514
+
.iter()
515
515
+
.any(|s| s.as_str() == resource_url)
516
516
+
{
535
517
return Err(ResolverError::authorization_server_metadata(
536
518
smol_str::format_smolstr!(
537
519
"pds {pds}, resource {0} not protected by issuer: {issuer}, protected resources: {1:?}",
···
559
541
#[cfg(target_arch = "wasm32")]
560
542
async fn get_resource_server_metadata_impl<T: OAuthResolver + ?Sized>(
561
543
resolver: &T,
562
562
-
pds: &CowStr<'_>,
563
563
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
544
544
+
pds: &str,
545
545
+
) -> Result<OAuthAuthorizationServerMetadata> {
564
546
let rs_metadata = resolve_protected_resource_info(resolver, pds).await?;
565
547
// ATPROTO requires one, and only one, authorization server entry
566
548
// > That document MUST contain a single item in the authorization_servers array.
···
582
564
));
583
565
}
584
566
};
585
585
-
let as_metadata = resolver.get_authorization_server_metadata(issuer).await?;
567
567
+
let as_metadata = resolver
568
568
+
.get_authorization_server_metadata(issuer.as_ref())
569
569
+
.await?;
586
570
// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-08#name-authorization-server-metada
587
571
if let Some(protected_resources) = &as_metadata.protected_resources {
588
572
let resource_url = rs_metadata
589
573
.resource
590
574
.strip_suffix('/')
591
575
.unwrap_or(rs_metadata.resource.as_str());
592
592
-
if !protected_resources.contains(&CowStr::Borrowed(resource_url)) {
576
576
+
if !protected_resources
577
577
+
.iter()
578
578
+
.any(|s| s.as_str() == resource_url)
579
579
+
{
593
580
return Err(ResolverError::authorization_server_metadata(
594
581
smol_str::format_smolstr!(
595
582
"pds {pds}, resource {0} not protected by issuer: {issuer}, protected resources: {1:?}",
···
628
615
pub trait OAuthResolver: IdentityResolver + HttpClient {
629
616
/// Verify that the authorization server in `server_metadata` is the correct issuer for `sub`.
630
617
#[cfg(not(target_arch = "wasm32"))]
631
631
-
fn verify_issuer(
618
618
+
fn verify_issuer<S: BosStr + Sync>(
632
619
&self,
633
633
-
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
634
634
-
sub: &Did<'_>,
620
620
+
server_metadata: &OAuthAuthorizationServerMetadata,
621
621
+
sub: &Did<S>,
635
622
) -> impl Future<Output = Result<Uri<String>>> + Send
636
623
where
637
624
Self: Sync,
···
641
628
642
629
/// Verify that the authorization server in `server_metadata` is the correct issuer for `sub`.
643
630
#[cfg(target_arch = "wasm32")]
644
644
-
fn verify_issuer(
631
631
+
fn verify_issuer<S: BosStr>(
645
632
&self,
646
646
-
server_metadata: &OAuthAuthorizationServerMetadata<'_>,
647
647
-
sub: &Did<'_>,
633
633
+
server_metadata: &OAuthAuthorizationServerMetadata,
634
634
+
sub: &Did<S>,
648
635
) -> impl Future<Output = Result<Uri<String>>> {
649
636
verify_issuer_impl(self, server_metadata, sub)
650
637
}
···
659
646
fn resolve_oauth(
660
647
&self,
661
648
input: &str,
662
662
-
) -> impl Future<
663
663
-
Output = Result<(
664
664
-
OAuthAuthorizationServerMetadata<'static>,
665
665
-
Option<DidDocument<'static>>,
666
666
-
)>,
667
667
-
> + Send
649
649
+
) -> impl Future<Output = Result<(OAuthAuthorizationServerMetadata, Option<DidDocument>)>> + Send
668
650
where
669
651
Self: Sync,
670
652
{
···
681
663
fn resolve_oauth(
682
664
&self,
683
665
input: &str,
684
684
-
) -> impl Future<
685
685
-
Output = Result<(
686
686
-
OAuthAuthorizationServerMetadata<'static>,
687
687
-
Option<DidDocument<'static>>,
688
688
-
)>,
689
689
-
> {
666
666
+
) -> impl Future<Output = Result<(OAuthAuthorizationServerMetadata, Option<DidDocument>)>> {
690
667
resolve_oauth_impl(self, input)
691
668
}
692
669
···
697
674
#[cfg(not(target_arch = "wasm32"))]
698
675
fn resolve_from_service(
699
676
&self,
700
700
-
input: &CowStr<'_>,
701
701
-
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>>> + Send
677
677
+
input: &str,
678
678
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata>> + Send
702
679
where
703
680
Self: Sync,
704
681
{
···
712
689
#[cfg(target_arch = "wasm32")]
713
690
fn resolve_from_service(
714
691
&self,
715
715
-
input: &CowStr<'_>,
716
716
-
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>>> {
692
692
+
input: &str,
693
693
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata>> {
717
694
resolve_from_service_impl(self, input)
718
695
}
719
696
···
722
699
fn resolve_from_identity(
723
700
&self,
724
701
input: &str,
725
725
-
) -> impl Future<
726
726
-
Output = Result<(
727
727
-
OAuthAuthorizationServerMetadata<'static>,
728
728
-
DidDocument<'static>,
729
729
-
)>,
730
730
-
> + Send
702
702
+
) -> impl Future<Output = Result<(OAuthAuthorizationServerMetadata, DidDocument)>> + Send
731
703
where
732
704
Self: Sync,
733
705
{
···
739
711
fn resolve_from_identity(
740
712
&self,
741
713
input: &str,
742
742
-
) -> impl Future<
743
743
-
Output = Result<(
744
744
-
OAuthAuthorizationServerMetadata<'static>,
745
745
-
DidDocument<'static>,
746
746
-
)>,
747
747
-
> {
714
714
+
) -> impl Future<Output = Result<(OAuthAuthorizationServerMetadata, DidDocument)>> {
748
715
resolve_from_identity_impl(self, input)
749
716
}
750
717
···
755
722
#[cfg(not(target_arch = "wasm32"))]
756
723
fn get_authorization_server_metadata(
757
724
&self,
758
758
-
issuer: &CowStr<'_>,
759
759
-
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>>> + Send
725
725
+
issuer: &str,
726
726
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata>> + Send
760
727
where
761
728
Self: Sync,
762
729
{
···
770
737
#[cfg(target_arch = "wasm32")]
771
738
fn get_authorization_server_metadata(
772
739
&self,
773
773
-
issuer: &CowStr<'_>,
774
774
-
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>>> {
740
740
+
issuer: &str,
741
741
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata>> {
775
742
get_authorization_server_metadata_impl(self, issuer)
776
743
}
777
744
···
779
746
#[cfg(not(target_arch = "wasm32"))]
780
747
fn get_resource_server_metadata(
781
748
&self,
782
782
-
pds: &CowStr<'_>,
783
783
-
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>>> + Send
749
749
+
pds: &str,
750
750
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata>> + Send
784
751
where
785
752
Self: Sync,
786
753
{
···
791
758
#[cfg(target_arch = "wasm32")]
792
759
fn get_resource_server_metadata(
793
760
&self,
794
794
-
pds: &CowStr<'_>,
795
795
-
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata<'static>>> {
761
761
+
pds: &str,
762
762
+
) -> impl Future<Output = Result<OAuthAuthorizationServerMetadata>> {
796
763
get_resource_server_metadata_impl(self, pds)
797
764
}
798
765
}
···
803
770
/// this prevents a compromised server from claiming to be a different issuer.
804
771
pub async fn resolve_authorization_server<T: HttpClient + ?Sized>(
805
772
client: &T,
806
806
-
server: &CowStr<'_>,
807
807
-
) -> Result<OAuthAuthorizationServerMetadata<'static>> {
773
773
+
server: &str,
774
774
+
) -> Result<OAuthAuthorizationServerMetadata> {
808
775
let url = format!(
809
776
"{}/.well-known/oauth-authorization-server",
810
777
server.trim_end_matches("/")
···
821
788
if res.status() == StatusCode::OK {
822
789
let metadata = serde_json::from_slice::<OAuthAuthorizationServerMetadata>(res.body())?;
823
790
// https://datatracker.ietf.org/doc/html/rfc8414#section-3.3
824
824
-
if metadata.issuer == server.as_str() {
791
791
+
if metadata.issuer.as_str() == server {
825
792
Ok(metadata.into_static())
826
793
} else {
827
794
Err(ResolverError::authorization_server_metadata(
···
839
806
/// that the metadata belongs to the PDS we queried and not a different resource.
840
807
pub async fn resolve_protected_resource_info<T: HttpClient + ?Sized>(
841
808
client: &T,
842
842
-
server: &CowStr<'_>,
843
843
-
) -> Result<OAuthProtectedResourceMetadata<'static>> {
809
809
+
server: &str,
810
810
+
) -> Result<OAuthProtectedResourceMetadata> {
844
811
let url = format!(
845
812
"{}/.well-known/oauth-protected-resource",
846
813
server.trim_end_matches("/")
···
857
824
if res.status() == StatusCode::OK {
858
825
let metadata = serde_json::from_slice::<OAuthProtectedResourceMetadata>(res.body())?;
859
826
// https://datatracker.ietf.org/doc/html/rfc8414#section-3.3
860
860
-
if metadata.resource == server.as_str() {
827
827
+
if metadata.resource.as_str() == server {
861
828
Ok(metadata.into_static())
862
829
} else {
863
830
Err(ResolverError::authorization_server_metadata(
···
878
845
879
846
use super::*;
880
847
use http::{Request as HttpRequest, Response as HttpResponse, StatusCode};
881
881
-
use jacquard_common::http_client::HttpClient;
848
848
+
use jacquard_common::{CowStr, http_client::HttpClient};
882
849
use tokio::sync::Mutex;
883
850
884
851
#[derive(Default, Clone)]
···
21
21
22
22
use std::collections::{BTreeMap, BTreeSet};
23
23
use std::fmt;
24
24
+
use std::marker::PhantomData;
24
25
use std::str::FromStr;
25
26
27
27
+
use jacquard_common::bos::{BosStr, DefaultStr};
26
28
use jacquard_common::types::did::Did;
27
29
use jacquard_common::types::nsid::Nsid;
28
30
use jacquard_common::types::string::AtStrError;
29
29
-
use jacquard_common::{CowStr, IntoStatic};
31
31
+
use jacquard_common::{Bos, FromStaticStr, IntoStatic};
30
32
use serde::de::Visitor;
31
33
use serde::{Deserialize, Serialize};
32
32
-
use smol_str::{SmolStr, ToSmolStr};
34
34
+
use smol_str::{SmolStr, SmolStrBuilder, ToSmolStr, format_smolstr};
33
35
34
36
/// Represents an AT Protocol OAuth scope
35
37
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36
36
-
pub enum Scope<'s> {
38
38
+
pub enum Scope<S: BosStr = DefaultStr> {
37
39
/// Account scope for accessing account information
38
40
Account(AccountScope),
39
41
/// Identity scope for accessing identity information
40
42
Identity(IdentityScope),
41
43
/// Blob scope for blob operations with mime type constraints
42
42
-
Blob(BlobScope<'s>),
44
44
+
Blob(BlobScope<S>),
43
45
/// Repository scope for collection operations
44
44
-
Repo(RepoScope<'s>),
46
46
+
Repo(RepoScope<S>),
45
47
/// RPC scope for method access
46
46
-
Rpc(RpcScope<'s>),
48
48
+
Rpc(RpcScope<S>),
47
49
/// AT Protocol scope - required to indicate that other AT Protocol scopes will be used
48
50
Atproto,
49
51
/// Transition scope for migration operations
···
56
58
Email,
57
59
}
58
60
59
59
-
impl Serialize for Scope<'_> {
60
60
-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
61
61
+
impl<S: BosStr + Ord> Serialize for Scope<S> {
62
62
+
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
61
63
where
62
62
-
S: serde::Serializer,
64
64
+
Ser: serde::Serializer,
63
65
{
64
66
serializer.serialize_str(&self.to_string_normalized())
65
67
}
66
68
}
67
69
68
68
-
impl<'de> Deserialize<'de> for Scope<'_> {
70
70
+
impl<'de, S> Deserialize<'de> for Scope<S>
71
71
+
where
72
72
+
S: BosStr + Ord + Deserialize<'de> + FromStr,
73
73
+
<S as FromStr>::Err: core::fmt::Debug,
74
74
+
{
69
75
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
70
76
where
71
77
D: serde::Deserializer<'de>,
72
78
{
73
73
-
struct ScopeVisitor;
79
79
+
struct ScopeVisitor<St: BosStr + Ord + FromStr>(PhantomData<St>);
74
80
75
75
-
impl Visitor<'_> for ScopeVisitor {
76
76
-
type Value = Scope<'static>;
81
81
+
impl<St: BosStr + Ord + FromStr> Visitor<'_> for ScopeVisitor<St>
82
82
+
where
83
83
+
<St as FromStr>::Err: core::fmt::Debug,
84
84
+
{
85
85
+
type Value = Scope<St>;
77
86
78
87
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
79
88
write!(formatter, "a scope string")
···
82
91
where
83
92
E: serde::de::Error,
84
93
{
85
85
-
Scope::parse(v)
86
86
-
.map(|s| s.into_static())
87
87
-
.map_err(|e| serde::de::Error::custom(format!("{:?}", e)))
94
94
+
Scope::parse(v).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))
88
95
}
89
96
}
90
90
-
deserializer.deserialize_str(ScopeVisitor)
97
97
+
deserializer
98
98
+
.deserialize_str(ScopeVisitor(PhantomData))
99
99
+
.map(|scope| scope)
91
100
}
92
101
}
93
102
94
94
-
impl IntoStatic for Scope<'_> {
95
95
-
type Output = Scope<'static>;
103
103
+
impl<S: BosStr + Ord + IntoStatic> IntoStatic for Scope<S>
104
104
+
where
105
105
+
S::Output: BosStr + Ord,
106
106
+
{
107
107
+
type Output = Scope<S::Output>;
96
108
97
109
fn into_static(self) -> Self::Output {
98
110
match self {
···
159
171
160
172
/// Blob scope with mime type constraints
161
173
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
162
162
-
pub struct BlobScope<'s> {
174
174
+
pub struct BlobScope<S: BosStr = DefaultStr> {
163
175
/// Accepted mime types
164
164
-
pub accept: BTreeSet<MimePattern<'s>>,
176
176
+
pub accept: BTreeSet<MimePattern<S>>,
165
177
}
166
178
167
167
-
impl IntoStatic for BlobScope<'_> {
168
168
-
type Output = BlobScope<'static>;
179
179
+
impl<S: BosStr + AsRef<str> + Ord> BlobScope<S> {
180
180
+
/// Convert to a `BlobScope` with a different backing type.
181
181
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr + Ord>(self) -> BlobScope<B> {
182
182
+
BlobScope {
183
183
+
accept: self.accept.into_iter().map(|p| p.convert()).collect(),
184
184
+
}
185
185
+
}
186
186
+
}
187
187
+
188
188
+
impl<S: BosStr + IntoStatic> IntoStatic for BlobScope<S>
189
189
+
where
190
190
+
S::Output: BosStr,
191
191
+
MimePattern<S::Output>: Ord,
192
192
+
{
193
193
+
type Output = BlobScope<S::Output>;
169
194
170
195
fn into_static(self) -> Self::Output {
171
196
BlobScope {
···
176
201
177
202
/// MIME type pattern for blob scope
178
203
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
179
179
-
pub enum MimePattern<'s> {
204
204
+
pub enum MimePattern<S: BosStr = DefaultStr> {
180
205
/// Match all types
181
206
All,
182
207
/// Match all subtypes of a type (e.g., "image/*")
183
183
-
TypeWildcard(CowStr<'s>),
208
208
+
TypeWildcard(S),
184
209
/// Exact mime type match
185
185
-
Exact(CowStr<'s>),
210
210
+
Exact(S),
186
211
}
187
212
188
188
-
impl IntoStatic for MimePattern<'_> {
189
189
-
type Output = MimePattern<'static>;
213
213
+
impl<S: BosStr> MimePattern<S> {
214
214
+
/// Convert to a `MimePattern` with a different backing type.
215
215
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr>(self) -> MimePattern<B> {
216
216
+
match self {
217
217
+
MimePattern::All => MimePattern::All,
218
218
+
MimePattern::TypeWildcard(s) => MimePattern::TypeWildcard(s.into()),
219
219
+
MimePattern::Exact(s) => MimePattern::Exact(s.into()),
220
220
+
}
221
221
+
}
222
222
+
}
223
223
+
224
224
+
impl<S: BosStr + IntoStatic> IntoStatic for MimePattern<S>
225
225
+
where
226
226
+
S::Output: BosStr,
227
227
+
{
228
228
+
type Output = MimePattern<S::Output>;
190
229
191
230
fn into_static(self) -> Self::Output {
192
231
match self {
···
199
238
200
239
/// Repository scope with collection and action constraints
201
240
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
202
202
-
pub struct RepoScope<'s> {
241
241
+
pub struct RepoScope<S: BosStr = DefaultStr> {
203
242
/// Collection NSID or wildcard
204
204
-
pub collection: RepoCollection<'s>,
243
243
+
pub collection: RepoCollection<S>,
205
244
/// Allowed actions
206
245
pub actions: BTreeSet<RepoAction>,
207
246
}
208
247
209
209
-
impl IntoStatic for RepoScope<'_> {
210
210
-
type Output = RepoScope<'static>;
248
248
+
impl<S: BosStr + Ord> RepoScope<S> {
249
249
+
/// Convert to a `RepoScope` with a different backing type.
250
250
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr + Ord>(self) -> RepoScope<B> {
251
251
+
RepoScope {
252
252
+
collection: self.collection.convert(),
253
253
+
actions: self.actions,
254
254
+
}
255
255
+
}
256
256
+
}
257
257
+
258
258
+
impl<S: BosStr + IntoStatic> IntoStatic for RepoScope<S>
259
259
+
where
260
260
+
S::Output: BosStr,
261
261
+
{
262
262
+
type Output = RepoScope<S::Output>;
211
263
212
264
fn into_static(self) -> Self::Output {
213
265
RepoScope {
···
219
271
220
272
/// Repository collection identifier
221
273
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
222
222
-
pub enum RepoCollection<'s> {
274
274
+
pub enum RepoCollection<S: BosStr = DefaultStr> {
223
275
/// All collections (wildcard)
224
276
All,
225
277
/// Specific collection NSID
226
226
-
Nsid(Nsid<'s>),
278
278
+
Nsid(Nsid<S>),
279
279
+
}
280
280
+
281
281
+
impl<S: BosStr> RepoCollection<S> {
282
282
+
/// Convert to an `Nsid` with a different backing type.
283
283
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr>(self) -> RepoCollection<B> {
284
284
+
match self {
285
285
+
RepoCollection::All => RepoCollection::All,
286
286
+
RepoCollection::Nsid(nsid) => RepoCollection::Nsid(nsid.convert()),
287
287
+
}
288
288
+
}
227
289
}
228
290
229
229
-
impl IntoStatic for RepoCollection<'_> {
230
230
-
type Output = RepoCollection<'static>;
291
291
+
impl<S: BosStr + IntoStatic> IntoStatic for RepoCollection<S>
292
292
+
where
293
293
+
S::Output: BosStr,
294
294
+
{
295
295
+
type Output = RepoCollection<S::Output>;
231
296
232
297
fn into_static(self) -> Self::Output {
233
298
match self {
···
250
315
251
316
/// RPC scope with lexicon method and audience constraints
252
317
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
253
253
-
pub struct RpcScope<'s> {
318
318
+
pub struct RpcScope<S: BosStr = DefaultStr> {
254
319
/// Lexicon methods (NSIDs or wildcard)
255
255
-
pub lxm: BTreeSet<RpcLexicon<'s>>,
320
320
+
pub lxm: BTreeSet<RpcLexicon<S>>,
256
321
/// Audiences (DIDs or wildcard)
257
257
-
pub aud: BTreeSet<RpcAudience<'s>>,
322
322
+
pub aud: BTreeSet<RpcAudience<S>>,
323
323
+
}
324
324
+
325
325
+
impl<S: BosStr + Ord> RpcScope<S> {
326
326
+
/// Convert to a `RpcScope` with a different backing type.
327
327
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr + Ord>(self) -> RpcScope<B> {
328
328
+
RpcScope {
329
329
+
lxm: self.lxm.into_iter().map(|s| s.convert()).collect(),
330
330
+
aud: self.aud.into_iter().map(|s| s.convert()).collect(),
331
331
+
}
332
332
+
}
258
333
}
259
334
260
260
-
impl IntoStatic for RpcScope<'_> {
261
261
-
type Output = RpcScope<'static>;
335
335
+
impl<S: BosStr + IntoStatic> IntoStatic for RpcScope<S>
336
336
+
where
337
337
+
S::Output: BosStr,
338
338
+
RpcLexicon<S::Output>: Ord,
339
339
+
RpcAudience<S::Output>: Ord,
340
340
+
{
341
341
+
type Output = RpcScope<S::Output>;
262
342
263
343
fn into_static(self) -> Self::Output {
264
344
RpcScope {
···
270
350
271
351
/// RPC lexicon identifier
272
352
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
273
273
-
pub enum RpcLexicon<'s> {
353
353
+
pub enum RpcLexicon<S: BosStr = DefaultStr> {
274
354
/// All lexicons (wildcard)
275
355
All,
276
356
/// Specific lexicon NSID
277
277
-
Nsid(Nsid<'s>),
357
357
+
Nsid(Nsid<S>),
358
358
+
}
359
359
+
360
360
+
impl<S: BosStr> RpcLexicon<S> {
361
361
+
/// Convert to an `Nsid` with a different backing type.
362
362
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr>(self) -> RpcLexicon<B> {
363
363
+
match self {
364
364
+
RpcLexicon::All => RpcLexicon::All,
365
365
+
RpcLexicon::Nsid(nsid) => RpcLexicon::Nsid(nsid.convert()),
366
366
+
}
367
367
+
}
278
368
}
279
369
280
280
-
impl IntoStatic for RpcLexicon<'_> {
281
281
-
type Output = RpcLexicon<'static>;
370
370
+
impl<S: BosStr + IntoStatic> IntoStatic for RpcLexicon<S>
371
371
+
where
372
372
+
S::Output: BosStr,
373
373
+
{
374
374
+
type Output = RpcLexicon<S::Output>;
282
375
283
376
fn into_static(self) -> Self::Output {
284
377
match self {
···
290
383
291
384
/// RPC audience identifier
292
385
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
293
293
-
pub enum RpcAudience<'s> {
386
386
+
pub enum RpcAudience<S: BosStr = DefaultStr> {
294
387
/// All audiences (wildcard)
295
388
All,
296
389
/// Specific DID
297
297
-
Did(Did<'s>),
390
390
+
Did(Did<S>),
391
391
+
}
392
392
+
393
393
+
impl<S: BosStr> RpcAudience<S> {
394
394
+
/// Convert to an `Nsid` with a different backing type.
395
395
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr>(self) -> RpcAudience<B> {
396
396
+
match self {
397
397
+
RpcAudience::All => RpcAudience::All,
398
398
+
RpcAudience::Did(did) => RpcAudience::Did(did.convert()),
399
399
+
}
400
400
+
}
298
401
}
299
402
300
300
-
impl IntoStatic for RpcAudience<'_> {
301
301
-
type Output = RpcAudience<'static>;
403
403
+
impl<S: BosStr + IntoStatic> IntoStatic for RpcAudience<S>
404
404
+
where
405
405
+
S::Output: BosStr,
406
406
+
{
407
407
+
type Output = RpcAudience<S::Output>;
302
408
303
409
fn into_static(self) -> Self::Output {
304
410
match self {
···
308
414
}
309
415
}
310
416
311
311
-
impl<'s> Scope<'s> {
417
417
+
impl<S: BosStr + Ord> Scope<S> {
418
418
+
/// Convert to a `Scope` with a different backing type.
419
419
+
pub fn convert<B: Bos<str> + From<S> + AsRef<str> + FromStaticStr + Ord>(self) -> Scope<B> {
420
420
+
match self {
421
421
+
Scope::Account(scope) => Scope::Account(scope),
422
422
+
Scope::Identity(scope) => Scope::Identity(scope),
423
423
+
Scope::Blob(scope) => Scope::Blob(scope.convert()),
424
424
+
Scope::Repo(scope) => Scope::Repo(scope.convert()),
425
425
+
Scope::Rpc(scope) => Scope::Rpc(scope.convert()),
426
426
+
Scope::Atproto => Scope::Atproto,
427
427
+
Scope::Transition(scope) => Scope::Transition(scope),
428
428
+
Scope::OpenId => Scope::OpenId,
429
429
+
Scope::Profile => Scope::Profile,
430
430
+
Scope::Email => Scope::Email,
431
431
+
}
432
432
+
}
433
433
+
312
434
/// Parse multiple space-separated scopes from a string
313
435
///
314
436
/// # Examples
···
317
439
/// let scopes = Scope::parse_multiple("atproto repo:*").unwrap();
318
440
/// assert_eq!(scopes.len(), 2);
319
441
/// ```
320
320
-
pub fn parse_multiple(s: &'s str) -> Result<Vec<Self>, ParseError> {
442
442
+
pub fn parse_multiple<'a>(s: &'a str) -> Result<Vec<Self>, ParseError>
443
443
+
where
444
444
+
S: FromStr,
445
445
+
<S as FromStr>::Err: core::fmt::Debug,
446
446
+
{
321
447
if s.trim().is_empty() {
322
448
return Ok(Vec::new());
323
449
}
···
342
468
/// let scopes = Scope::parse_multiple_reduced("atproto repo:app.bsky.feed.post repo:*").unwrap();
343
469
/// assert_eq!(scopes.len(), 2); // atproto and repo:*
344
470
/// ```
345
345
-
pub fn parse_multiple_reduced(s: &'s str) -> Result<Vec<Self>, ParseError> {
471
471
+
pub fn parse_multiple_reduced<'a>(s: &'a str) -> Result<Vec<Self>, ParseError>
472
472
+
where
473
473
+
S: FromStr,
474
474
+
<S as FromStr>::Err: core::fmt::Debug,
475
475
+
{
346
476
let all_scopes = Self::parse_multiple(s)?;
347
477
348
478
if all_scopes.is_empty() {
···
403
533
/// let result = Scope::serialize_multiple(&scopes);
404
534
/// assert_eq!(result, "account:email atproto repo:*");
405
535
/// ```
406
406
-
pub fn serialize_multiple(scopes: &[Self]) -> CowStr<'static> {
536
536
+
pub fn serialize_multiple(scopes: &[Self]) -> SmolStr {
407
537
if scopes.is_empty() {
408
408
-
return CowStr::default();
538
538
+
return SmolStr::new_static("");
409
539
}
410
540
411
411
-
let mut serialized: Vec<String> = scopes
541
541
+
let mut serialized: Vec<SmolStr> = scopes
412
542
.iter()
413
543
.map(|scope| scope.to_string_normalized())
414
544
.collect();
415
545
416
546
serialized.sort();
417
417
-
serialized.join(" ").into()
547
547
+
let mut builder = SmolStrBuilder::new();
548
548
+
for (i, scope) in serialized.iter().enumerate() {
549
549
+
if i > 0 {
550
550
+
builder.push_str(" ");
551
551
+
}
552
552
+
builder.push_str(scope);
553
553
+
}
554
554
+
builder.finish()
418
555
}
419
556
420
557
/// Remove a scope from a list of scopes
···
435
572
/// assert_eq!(result.len(), 2);
436
573
/// assert!(!result.contains(&to_remove));
437
574
/// ```
438
438
-
pub fn remove_scope(scopes: &[Self], scope_to_remove: &Self) -> Vec<Self> {
575
575
+
pub fn remove_scope(scopes: &[Self], scope_to_remove: &Self) -> Vec<Self>
576
576
+
where
577
577
+
S: Clone,
578
578
+
{
439
579
scopes
440
580
.iter()
441
581
.filter(|s| *s != scope_to_remove)
···
444
584
}
445
585
446
586
/// Parse a scope from a string
447
447
-
pub fn parse(s: &'s str) -> Result<Self, ParseError> {
587
587
+
pub fn parse<'a>(s: &'a str) -> Result<Self, ParseError>
588
588
+
where
589
589
+
S: FromStr,
590
590
+
<S as FromStr>::Err: core::fmt::Debug,
591
591
+
{
448
592
// Determine the prefix first by checking for known prefixes
449
593
let prefixes = [
450
594
"account",
···
500
644
}
501
645
}
502
646
503
503
-
fn parse_account(suffix: Option<&'s str>) -> Result<Self, ParseError> {
647
647
+
fn parse_account(suffix: Option<&str>) -> Result<Self, ParseError> {
504
648
let (resource_str, params) = match suffix {
505
649
Some(s) => {
506
650
if let Some(pos) = s.find('?') {
···
538
682
Ok(Scope::Account(AccountScope { resource, action }))
539
683
}
540
684
541
541
-
fn parse_identity(suffix: Option<&'s str>) -> Result<Self, ParseError> {
685
685
+
fn parse_identity(suffix: Option<&str>) -> Result<Self, ParseError> {
542
686
let scope = match suffix {
543
687
Some("handle") => IdentityScope::Handle,
544
688
Some("*") => IdentityScope::All,
···
549
693
Ok(Scope::Identity(scope))
550
694
}
551
695
552
552
-
fn parse_blob(suffix: Option<&'s str>) -> Result<Self, ParseError> {
553
553
-
let mut accept = BTreeSet::new();
696
696
+
fn parse_blob<'a>(suffix: Option<&'a str>) -> Result<Self, ParseError>
697
697
+
where
698
698
+
S: FromStr,
699
699
+
<S as FromStr>::Err: core::fmt::Debug,
700
700
+
{
701
701
+
let mut accept: BTreeSet<MimePattern<S>> = BTreeSet::new();
554
702
555
703
match suffix {
556
704
Some(s) if s.starts_with('?') => {
557
705
let params = parse_query_string(&s[1..]);
558
706
if let Some(values) = params.get("accept") {
559
707
for value in values {
560
560
-
accept.insert(MimePattern::from_str(value)?);
708
708
+
accept.insert(MimePattern::from_str(*value)?);
561
709
}
562
710
}
563
711
}
···
576
724
Ok(Scope::Blob(BlobScope { accept }))
577
725
}
578
726
579
579
-
fn parse_repo(suffix: Option<&'s str>) -> Result<Self, ParseError> {
727
727
+
fn parse_repo<'a>(suffix: Option<&'a str>) -> Result<Self, ParseError>
728
728
+
where
729
729
+
S: FromStr,
730
730
+
{
580
731
let (collection_str, params) = match suffix {
581
732
Some(s) => {
582
733
if let Some(pos) = s.find('?') {
···
590
741
591
742
let collection = match collection_str {
592
743
Some("*") | None => RepoCollection::All,
593
593
-
Some(nsid) => RepoCollection::Nsid(Nsid::new(nsid)?),
744
744
+
Some(nsid) => RepoCollection::Nsid(Nsid::from_str(nsid)?),
594
745
};
595
746
596
747
let mut actions = BTreeSet::new();
···
631
782
}))
632
783
}
633
784
634
634
-
fn parse_rpc(suffix: Option<&'s str>) -> Result<Self, ParseError> {
785
785
+
fn parse_rpc<'a>(suffix: Option<&'a str>) -> Result<Self, ParseError>
786
786
+
where
787
787
+
S: FromStr,
788
788
+
{
635
789
let mut lxm = BTreeSet::new();
636
790
let mut aud = BTreeSet::new();
637
791
···
645
799
646
800
if let Some(values) = params.get("lxm") {
647
801
for value in values {
648
648
-
if value.as_ref() == "*" {
802
802
+
if *value == "*" {
649
803
lxm.insert(RpcLexicon::All);
650
804
} else {
651
651
-
lxm.insert(RpcLexicon::Nsid(Nsid::new(value)?.into_static()));
805
805
+
lxm.insert(RpcLexicon::Nsid(Nsid::from_str(*value)?));
652
806
}
653
807
}
654
808
}
655
809
656
810
if let Some(values) = params.get("aud") {
657
811
for value in values {
658
658
-
if value.as_ref() == "*" {
812
812
+
if *value == "*" {
659
813
aud.insert(RpcAudience::All);
660
814
} else {
661
661
-
aud.insert(RpcAudience::Did(Did::new(value)?.into_static()));
815
815
+
aud.insert(RpcAudience::Did(Did::from_str(*value)?));
662
816
}
663
817
}
664
818
}
···
669
823
let nsid = &s[..pos];
670
824
let params = parse_query_string(&s[pos + 1..]);
671
825
672
672
-
lxm.insert(RpcLexicon::Nsid(Nsid::new(nsid)?.into_static()));
826
826
+
lxm.insert(RpcLexicon::Nsid(Nsid::from_str(nsid)?));
673
827
674
828
if let Some(values) = params.get("aud") {
675
829
for value in values {
676
676
-
if value.as_ref() == "*" {
830
830
+
if *value == "*" {
677
831
aud.insert(RpcAudience::All);
678
832
} else {
679
679
-
aud.insert(RpcAudience::Did(Did::new(value)?.into_static()));
833
833
+
aud.insert(RpcAudience::Did(Did::from_str(*value)?));
680
834
}
681
835
}
682
836
}
683
837
} else {
684
684
-
lxm.insert(RpcLexicon::Nsid(Nsid::new(s)?.into_static()));
838
838
+
lxm.insert(RpcLexicon::Nsid(Nsid::from_str(s)?));
685
839
}
686
840
}
687
841
None => {}
···
743
897
}
744
898
Ok(Scope::Email)
745
899
}
900
900
+
}
746
901
902
902
+
impl<S: BosStr + Ord> Scope<S> {
747
903
/// Convert the scope to its normalized string representation
748
748
-
pub fn to_string_normalized(&self) -> String {
904
904
+
pub fn to_string_normalized(&self) -> SmolStr {
749
905
match self {
750
906
Scope::Account(scope) => {
751
907
let resource = match scope.resource {
···
755
911
};
756
912
757
913
match scope.action {
758
758
-
AccountAction::Read => format!("account:{}", resource),
759
759
-
AccountAction::Manage => format!("account:{}?action=manage", resource),
914
914
+
AccountAction::Read => format_smolstr!("account:{}", resource),
915
915
+
AccountAction::Manage => format_smolstr!("account:{}?action=manage", resource),
760
916
}
761
917
}
762
918
Scope::Identity(scope) => match scope {
763
763
-
IdentityScope::Handle => "identity:handle".to_string(),
764
764
-
IdentityScope::All => "identity:*".to_string(),
919
919
+
IdentityScope::Handle => "identity:handle".to_smolstr(),
920
920
+
IdentityScope::All => "identity:*".to_smolstr(),
765
921
},
766
922
Scope::Blob(scope) => {
767
923
if scope.accept.len() == 1 {
768
924
if let Some(pattern) = scope.accept.iter().next() {
769
925
match pattern {
770
770
-
MimePattern::All => "blob:*/*".to_string(),
771
771
-
MimePattern::TypeWildcard(t) => format!("blob:{}/*", t),
772
772
-
MimePattern::Exact(mime) => format!("blob:{}", mime),
926
926
+
MimePattern::All => "blob:*/*".to_smolstr(),
927
927
+
MimePattern::TypeWildcard(t) => {
928
928
+
format_smolstr!("blob:{}/*", t.as_ref())
929
929
+
}
930
930
+
MimePattern::Exact(mime) => format_smolstr!("blob:{}", mime.as_ref()),
773
931
}
774
932
} else {
775
775
-
"blob:*/*".to_string()
933
933
+
"blob:*/*".to_smolstr()
776
934
}
777
935
} else {
778
936
let mut params = Vec::new();
779
937
for pattern in &scope.accept {
780
938
match pattern {
781
781
-
MimePattern::All => params.push("accept=*/*".to_string()),
782
782
-
MimePattern::TypeWildcard(t) => params.push(format!("accept={}/*", t)),
783
783
-
MimePattern::Exact(mime) => params.push(format!("accept={}", mime)),
939
939
+
MimePattern::All => params.push("accept=*/*".to_smolstr()),
940
940
+
MimePattern::TypeWildcard(t) => {
941
941
+
params.push(format_smolstr!("accept={}/*", t.as_ref()))
942
942
+
}
943
943
+
MimePattern::Exact(mime) => {
944
944
+
params.push(format_smolstr!("accept={}", mime.as_ref()))
945
945
+
}
784
946
}
785
947
}
786
948
params.sort();
787
787
-
format!("blob?{}", params.join("&"))
949
949
+
format_smolstr!("blob?{}", params.join("&"))
788
950
}
789
951
}
790
952
Scope::Repo(scope) => {
···
794
956
};
795
957
796
958
if scope.actions.len() == 3 {
797
797
-
format!("repo:{}", collection)
959
959
+
format_smolstr!("repo:{}", collection)
798
960
} else {
799
961
let mut params = Vec::new();
800
962
for action in &scope.actions {
···
804
966
RepoAction::Delete => params.push("action=delete"),
805
967
}
806
968
}
807
807
-
format!("repo:{}?{}", collection, params.join("&"))
969
969
+
format_smolstr!("repo:{}?{}", collection, params.join("&"))
808
970
}
809
971
}
810
972
Scope::Rpc(scope) => {
···
813
975
&& scope.aud.len() == 1
814
976
&& scope.aud.contains(&RpcAudience::All)
815
977
{
816
816
-
"rpc:*".to_string()
978
978
+
"rpc:*".to_smolstr()
817
979
} else if scope.lxm.len() == 1
818
980
&& scope.aud.len() == 1
819
981
&& scope.aud.contains(&RpcAudience::All)
820
982
{
821
983
if let Some(lxm) = scope.lxm.iter().next() {
822
984
match lxm {
823
823
-
RpcLexicon::All => "rpc:*".to_string(),
824
824
-
RpcLexicon::Nsid(nsid) => format!("rpc:{}", nsid),
985
985
+
RpcLexicon::All => "rpc:*".to_smolstr(),
986
986
+
RpcLexicon::Nsid(nsid) => format_smolstr!("rpc:{}", nsid),
825
987
}
826
988
} else {
827
827
-
"rpc:*".to_string()
989
989
+
"rpc:*".to_smolstr()
828
990
}
829
991
} else {
830
992
let mut params = Vec::new();
831
993
832
994
for lxm in &scope.lxm {
833
995
match lxm {
834
834
-
RpcLexicon::All => params.push("lxm=*".to_string()),
835
835
-
RpcLexicon::Nsid(nsid) => params.push(format!("lxm={}", nsid)),
996
996
+
RpcLexicon::All => params.push("lxm=*".to_smolstr()),
997
997
+
RpcLexicon::Nsid(nsid) => params.push(format_smolstr!("lxm={}", nsid)),
836
998
}
837
999
}
838
1000
839
1001
for aud in &scope.aud {
840
1002
match aud {
841
841
-
RpcAudience::All => params.push("aud=*".to_string()),
842
842
-
RpcAudience::Did(did) => params.push(format!("aud={}", did)),
1003
1003
+
RpcAudience::All => params.push("aud=*".to_smolstr()),
1004
1004
+
RpcAudience::Did(did) => params.push(format_smolstr!("aud={}", did)),
843
1005
}
844
1006
}
845
1007
846
1008
params.sort();
847
1009
848
1010
if params.is_empty() {
849
849
-
"rpc:*".to_string()
1011
1011
+
"rpc:*".to_smolstr()
850
1012
} else {
851
851
-
format!("rpc?{}", params.join("&"))
1013
1013
+
format_smolstr!("rpc?{}", params.join("&"))
852
1014
}
853
1015
}
854
1016
}
855
855
-
Scope::Atproto => "atproto".to_string(),
1017
1017
+
Scope::Atproto => "atproto".to_smolstr(),
856
1018
Scope::Transition(scope) => match scope {
857
857
-
TransitionScope::Generic => "transition:generic".to_string(),
858
858
-
TransitionScope::Email => "transition:email".to_string(),
1019
1019
+
TransitionScope::Generic => "transition:generic".to_smolstr(),
1020
1020
+
TransitionScope::Email => "transition:email".to_smolstr(),
859
1021
},
860
860
-
Scope::OpenId => "openid".to_string(),
861
861
-
Scope::Profile => "profile".to_string(),
862
862
-
Scope::Email => "email".to_string(),
1022
1022
+
Scope::OpenId => "openid".to_smolstr(),
1023
1023
+
Scope::Profile => "profile".to_smolstr(),
1024
1024
+
Scope::Email => "email".to_smolstr(),
863
1025
}
864
1026
}
865
1027
866
1028
/// Check if this scope grants the permissions of another scope
867
867
-
pub fn grants(&self, other: &Scope) -> bool {
1029
1029
+
pub fn grants<T: BosStr>(&self, other: &Scope<T>) -> bool {
868
1030
match (self, other) {
869
1031
// Atproto only grants itself (it's a required scope, not a permission grant)
870
1032
(Scope::Atproto, Scope::Atproto) => true,
···
916
1078
let collection_match = match (&a.collection, &b.collection) {
917
1079
(RepoCollection::All, _) => true,
918
1080
(RepoCollection::Nsid(a_nsid), RepoCollection::Nsid(b_nsid)) => {
919
919
-
a_nsid == b_nsid
1081
1081
+
// Compare as strings to support cross-type-parameter equality.
1082
1082
+
a_nsid.as_ref() == b_nsid.as_ref()
920
1083
}
921
1084
_ => false,
922
1085
};
···
928
1091
b.actions.is_subset(&a.actions) || a.actions.len() == 3
929
1092
}
930
1093
(Scope::Rpc(a), Scope::Rpc(b)) => {
931
931
-
let lxm_match = if a.lxm.contains(&RpcLexicon::All) {
1094
1094
+
let lxm_match = if a.lxm.iter().any(|l| matches!(l, RpcLexicon::All)) {
932
1095
true
933
1096
} else {
934
1097
b.lxm.iter().all(|b_lxm| match b_lxm {
935
1098
RpcLexicon::All => false,
936
936
-
RpcLexicon::Nsid(_) => a.lxm.contains(b_lxm),
1099
1099
+
// Compare as strings to support cross-type-parameter equality.
1100
1100
+
RpcLexicon::Nsid(b_nsid) => a.lxm.iter().any(|a_lxm| match a_lxm {
1101
1101
+
RpcLexicon::All => false,
1102
1102
+
RpcLexicon::Nsid(a_nsid) => a_nsid.as_ref() == b_nsid.as_ref(),
1103
1103
+
}),
937
1104
})
938
1105
};
939
1106
940
940
-
let aud_match = if a.aud.contains(&RpcAudience::All) {
1107
1107
+
let aud_match = if a.aud.iter().any(|a| matches!(a, RpcAudience::All)) {
941
1108
true
942
1109
} else {
943
1110
b.aud.iter().all(|b_aud| match b_aud {
944
1111
RpcAudience::All => false,
945
945
-
RpcAudience::Did(_) => a.aud.contains(b_aud),
1112
1112
+
// Compare as strings to support cross-type-parameter equality.
1113
1113
+
RpcAudience::Did(b_did) => a.aud.iter().any(|a_aud| match a_aud {
1114
1114
+
RpcAudience::All => false,
1115
1115
+
RpcAudience::Did(a_did) => a_did.as_ref() == b_did.as_ref(),
1116
1116
+
}),
946
1117
})
947
1118
};
948
1119
···
953
1124
}
954
1125
}
955
1126
956
956
-
impl MimePattern<'_> {
957
957
-
fn grants(&self, other: &MimePattern) -> bool {
1127
1127
+
impl<S: BosStr> MimePattern<S> {
1128
1128
+
fn grants<T: BosStr>(&self, other: &MimePattern<T>) -> bool {
958
1129
match (self, other) {
959
1130
(MimePattern::All, _) => true,
960
1131
(MimePattern::TypeWildcard(a_type), MimePattern::TypeWildcard(b_type)) => {
961
961
-
a_type == b_type
962
962
-
}
963
963
-
(MimePattern::TypeWildcard(a_type), MimePattern::Exact(b_mime)) => {
964
964
-
b_mime.starts_with(&format!("{}/", a_type))
1132
1132
+
// Compare as strings to support cross-type-parameter equality.
1133
1133
+
a_type.as_ref() == b_type.as_ref()
965
1134
}
966
966
-
(MimePattern::Exact(a), MimePattern::Exact(b)) => a == b,
1135
1135
+
(MimePattern::TypeWildcard(a_type), MimePattern::Exact(b_mime)) => b_mime
1136
1136
+
.as_ref()
1137
1137
+
.starts_with(&format!("{}/", a_type.as_ref())),
1138
1138
+
(MimePattern::Exact(a), MimePattern::Exact(b)) => a.as_ref() == b.as_ref(),
967
1139
_ => false,
968
1140
}
969
1141
}
970
1142
}
971
1143
972
972
-
impl FromStr for MimePattern<'_> {
1144
1144
+
impl<S: BosStr + FromStr> FromStr for MimePattern<S>
1145
1145
+
where
1146
1146
+
<S as FromStr>::Err: core::fmt::Debug,
1147
1147
+
{
973
1148
type Err = ParseError;
974
1149
975
1150
fn from_str(s: &str) -> Result<Self, Self::Err> {
976
1151
if s == "*/*" {
977
1152
Ok(MimePattern::All)
978
1153
} else if let Some(stripped) = s.strip_suffix("/*") {
979
979
-
Ok(MimePattern::TypeWildcard(CowStr::Owned(
980
980
-
stripped.to_smolstr(),
981
981
-
)))
1154
1154
+
Ok(MimePattern::TypeWildcard(S::from_str(stripped).unwrap()))
982
1155
} else if s.contains('/') {
983
983
-
Ok(MimePattern::Exact(CowStr::Owned(s.to_smolstr())))
1156
1156
+
Ok(MimePattern::Exact(S::from_str(s).unwrap()))
984
1157
} else {
985
1158
Err(ParseError::InvalidMimeType(s.to_string()))
986
1159
}
987
1160
}
988
1161
}
989
1162
990
990
-
impl FromStr for Scope<'_> {
991
991
-
type Err = ParseError;
1163
1163
+
impl<'a, S: BosStr + From<&'a str>> TryFrom<&'a str> for MimePattern<S> {
1164
1164
+
type Error = ParseError;
992
1165
993
993
-
fn from_str(s: &str) -> Result<Scope<'static>, Self::Err> {
994
994
-
match Scope::parse(s) {
995
995
-
Ok(parsed) => Ok(parsed.into_static()),
996
996
-
Err(e) => Err(e),
1166
1166
+
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
1167
1167
+
if s == "*/*" {
1168
1168
+
Ok(MimePattern::All)
1169
1169
+
} else if let Some(stripped) = s.strip_suffix("/*") {
1170
1170
+
Ok(MimePattern::TypeWildcard(S::from(stripped)))
1171
1171
+
} else if s.contains('/') {
1172
1172
+
Ok(MimePattern::Exact(S::from(s)))
1173
1173
+
} else {
1174
1174
+
Err(ParseError::InvalidMimeType(s.to_string()))
997
1175
}
998
1176
}
999
1177
}
1000
1178
1001
1001
-
impl fmt::Display for Scope<'_> {
1179
1179
+
impl<S: BosStr + Ord> fmt::Display for Scope<S> {
1002
1180
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1003
1181
write!(f, "{}", self.to_string_normalized())
1004
1182
}
1005
1183
}
1006
1184
1007
1185
/// Parse a query string into a map of keys to lists of values
1008
1008
-
fn parse_query_string(query: &str) -> BTreeMap<SmolStr, Vec<CowStr<'static>>> {
1186
1186
+
fn parse_query_string(query: &str) -> BTreeMap<SmolStr, Vec<&str>> {
1009
1187
let mut params = BTreeMap::new();
1010
1188
1011
1189
for pair in query.split('&') {
···
1015
1193
params
1016
1194
.entry(key.to_smolstr())
1017
1195
.or_insert_with(Vec::new)
1018
1018
-
.push(CowStr::Owned(value.to_smolstr()));
1196
1196
+
.push(value);
1019
1197
}
1020
1198
}
1021
1199
···
1059
1237
1060
1238
#[test]
1061
1239
fn test_account_scope_parsing() {
1062
1062
-
let scope = Scope::parse("account:email").unwrap();
1240
1240
+
let scope: Scope = Scope::parse("account:email").unwrap();
1063
1241
assert_eq!(
1064
1242
scope,
1065
1243
Scope::Account(AccountScope {
···
1068
1246
})
1069
1247
);
1070
1248
1071
1071
-
let scope = Scope::parse("account:repo?action=manage").unwrap();
1249
1249
+
let scope: Scope = Scope::parse("account:repo?action=manage").unwrap();
1072
1250
assert_eq!(
1073
1251
scope,
1074
1252
Scope::Account(AccountScope {
···
1077
1255
})
1078
1256
);
1079
1257
1080
1080
-
let scope = Scope::parse("account:status?action=read").unwrap();
1258
1258
+
let scope: Scope = Scope::parse("account:status?action=read").unwrap();
1081
1259
assert_eq!(
1082
1260
scope,
1083
1261
Scope::Account(AccountScope {
···
1089
1267
1090
1268
#[test]
1091
1269
fn test_identity_scope_parsing() {
1092
1092
-
let scope = Scope::parse("identity:handle").unwrap();
1270
1270
+
let scope: Scope = Scope::parse("identity:handle").unwrap();
1093
1271
assert_eq!(scope, Scope::Identity(IdentityScope::Handle));
1094
1272
1095
1095
-
let scope = Scope::parse("identity:*").unwrap();
1273
1273
+
let scope: Scope = Scope::parse("identity:*").unwrap();
1096
1274
assert_eq!(scope, Scope::Identity(IdentityScope::All));
1097
1275
}
1098
1276
1099
1277
#[test]
1100
1278
fn test_blob_scope_parsing() {
1101
1101
-
let scope = Scope::parse("blob:*/*").unwrap();
1279
1279
+
let scope: Scope = Scope::parse("blob:*/*").unwrap();
1102
1280
let mut accept = BTreeSet::new();
1103
1281
accept.insert(MimePattern::All);
1104
1282
assert_eq!(scope, Scope::Blob(BlobScope { accept }));
1105
1283
1106
1106
-
let scope = Scope::parse("blob:image/png").unwrap();
1284
1284
+
let scope: Scope<SmolStr> = Scope::parse("blob:image/png").unwrap();
1107
1285
let mut accept = BTreeSet::new();
1108
1108
-
accept.insert(MimePattern::Exact(CowStr::new_static("image/png")));
1286
1286
+
accept.insert(MimePattern::Exact(SmolStr::new_static("image/png")));
1109
1287
assert_eq!(scope, Scope::Blob(BlobScope { accept }));
1110
1288
1111
1289
let scope = Scope::parse("blob?accept=image/png&accept=image/jpeg").unwrap();
1112
1290
let mut accept = BTreeSet::new();
1113
1113
-
accept.insert(MimePattern::Exact(CowStr::new_static("image/png")));
1114
1114
-
accept.insert(MimePattern::Exact(CowStr::new_static("image/jpeg")));
1291
1291
+
accept.insert(MimePattern::Exact(SmolStr::new_static("image/png")));
1292
1292
+
accept.insert(MimePattern::Exact(SmolStr::new_static("image/jpeg")));
1115
1293
assert_eq!(scope, Scope::Blob(BlobScope { accept }));
1116
1294
1117
1295
let scope = Scope::parse("blob:image/*").unwrap();
1118
1296
let mut accept = BTreeSet::new();
1119
1119
-
accept.insert(MimePattern::TypeWildcard(CowStr::new_static("image")));
1297
1297
+
accept.insert(MimePattern::TypeWildcard(SmolStr::new_static("image")));
1120
1298
assert_eq!(scope, Scope::Blob(BlobScope { accept }));
1121
1299
}
1122
1300
1123
1301
#[test]
1124
1302
fn test_repo_scope_parsing() {
1125
1125
-
let scope = Scope::parse("repo:*?action=create").unwrap();
1303
1303
+
let scope: Scope<SmolStr> = Scope::parse("repo:*?action=create").unwrap();
1126
1304
let mut actions = BTreeSet::new();
1127
1305
actions.insert(RepoAction::Create);
1128
1306
assert_eq!(
···
1133
1311
})
1134
1312
);
1135
1313
1136
1136
-
let scope = Scope::parse("repo:app.bsky.feed.post?action=create&action=update").unwrap();
1314
1314
+
let scope: Scope =
1315
1315
+
Scope::parse("repo:app.bsky.feed.post?action=create&action=update").unwrap();
1137
1316
let mut actions = BTreeSet::new();
1138
1317
actions.insert(RepoAction::Create);
1139
1318
actions.insert(RepoAction::Update);
1140
1319
assert_eq!(
1141
1320
scope,
1142
1321
Scope::Repo(RepoScope {
1143
1143
-
collection: RepoCollection::Nsid(Nsid::new_static("app.bsky.feed.post").unwrap()),
1322
1322
+
collection: RepoCollection::Nsid(Nsid::new_owned("app.bsky.feed.post").unwrap()),
1144
1323
actions,
1145
1324
})
1146
1325
);
1147
1326
1148
1148
-
let scope = Scope::parse("repo:app.bsky.feed.post").unwrap();
1327
1327
+
let scope: Scope = Scope::parse("repo:app.bsky.feed.post").unwrap();
1149
1328
let mut actions = BTreeSet::new();
1150
1329
actions.insert(RepoAction::Create);
1151
1330
actions.insert(RepoAction::Update);
···
1153
1332
assert_eq!(
1154
1333
scope,
1155
1334
Scope::Repo(RepoScope {
1156
1156
-
collection: RepoCollection::Nsid(Nsid::new_static("app.bsky.feed.post").unwrap()),
1335
1335
+
collection: RepoCollection::Nsid(Nsid::new_owned("app.bsky.feed.post").unwrap()),
1157
1336
actions,
1158
1337
})
1159
1338
);
···
1161
1340
1162
1341
#[test]
1163
1342
fn test_rpc_scope_parsing() {
1164
1164
-
let scope = Scope::parse("rpc:*").unwrap();
1343
1343
+
let scope: Scope = Scope::parse("rpc:*").unwrap();
1165
1344
let mut lxm = BTreeSet::new();
1166
1345
let mut aud = BTreeSet::new();
1167
1346
lxm.insert(RpcLexicon::All);
1168
1347
aud.insert(RpcAudience::All);
1169
1348
assert_eq!(scope, Scope::Rpc(RpcScope { lxm, aud }));
1170
1349
1171
1171
-
let scope = Scope::parse("rpc:com.example.service").unwrap();
1350
1350
+
let scope: Scope<SmolStr> = Scope::parse("rpc:com.example.service").unwrap();
1172
1351
let mut lxm = BTreeSet::new();
1173
1352
let mut aud = BTreeSet::new();
1174
1353
lxm.insert(RpcLexicon::Nsid(
···
1177
1356
aud.insert(RpcAudience::All);
1178
1357
assert_eq!(scope, Scope::Rpc(RpcScope { lxm, aud }));
1179
1358
1180
1180
-
let scope =
1359
1359
+
let scope: Scope =
1181
1360
Scope::parse("rpc:com.example.service?aud=did:plc:yfvwmnlztr4dwkb7hwz55r2g").unwrap();
1182
1361
let mut lxm = BTreeSet::new();
1183
1362
let mut aud = BTreeSet::new();
1184
1363
lxm.insert(RpcLexicon::Nsid(
1185
1185
-
Nsid::new_static("com.example.service").unwrap(),
1364
1364
+
Nsid::new_owned("com.example.service").unwrap(),
1186
1365
));
1187
1366
aud.insert(RpcAudience::Did(
1188
1188
-
Did::new_static("did:plc:yfvwmnlztr4dwkb7hwz55r2g").unwrap(),
1367
1367
+
Did::new_owned("did:plc:yfvwmnlztr4dwkb7hwz55r2g").unwrap(),
1189
1368
));
1190
1369
assert_eq!(scope, Scope::Rpc(RpcScope { lxm, aud }));
1191
1370
1192
1192
-
let scope =
1371
1371
+
let scope: Scope =
1193
1372
Scope::parse("rpc?lxm=com.example.method1&lxm=com.example.method2&aud=did:plc:yfvwmnlztr4dwkb7hwz55r2g")
1194
1373
.unwrap();
1195
1374
let mut lxm = BTreeSet::new();
1196
1375
let mut aud = BTreeSet::new();
1197
1376
lxm.insert(RpcLexicon::Nsid(
1198
1198
-
Nsid::new_static("com.example.method1").unwrap(),
1377
1377
+
Nsid::new_owned("com.example.method1").unwrap(),
1199
1378
));
1200
1379
lxm.insert(RpcLexicon::Nsid(
1201
1201
-
Nsid::new_static("com.example.method2").unwrap(),
1380
1380
+
Nsid::new_owned("com.example.method2").unwrap(),
1202
1381
));
1203
1382
aud.insert(RpcAudience::Did(
1204
1204
-
Did::new_static("did:plc:yfvwmnlztr4dwkb7hwz55r2g").unwrap(),
1383
1383
+
Did::new_owned("did:plc:yfvwmnlztr4dwkb7hwz55r2g").unwrap(),
1205
1384
));
1206
1385
assert_eq!(scope, Scope::Rpc(RpcScope { lxm, aud }));
1207
1386
}
···
1226
1405
];
1227
1406
1228
1407
for (input, expected) in tests {
1229
1229
-
let scope = Scope::parse(input).unwrap();
1408
1408
+
let scope: Scope = Scope::parse(input).unwrap();
1230
1409
assert_eq!(scope.to_string_normalized(), expected);
1231
1410
}
1232
1411
}
1233
1412
1234
1413
#[test]
1235
1414
fn test_account_scope_grants() {
1236
1236
-
let manage = Scope::parse("account:email?action=manage").unwrap();
1237
1237
-
let read = Scope::parse("account:email?action=read").unwrap();
1238
1238
-
let other_read = Scope::parse("account:repo?action=read").unwrap();
1415
1415
+
let manage: Scope = Scope::parse("account:email?action=manage").unwrap();
1416
1416
+
let read: Scope = Scope::parse("account:email?action=read").unwrap();
1417
1417
+
let other_read: Scope = Scope::parse("account:repo?action=read").unwrap();
1239
1418
1240
1419
assert!(manage.grants(&read));
1241
1420
assert!(manage.grants(&manage));
···
1246
1425
1247
1426
#[test]
1248
1427
fn test_identity_scope_grants() {
1249
1249
-
let all = Scope::parse("identity:*").unwrap();
1250
1250
-
let handle = Scope::parse("identity:handle").unwrap();
1428
1428
+
let all: Scope = Scope::parse("identity:*").unwrap();
1429
1429
+
let handle: Scope = Scope::parse("identity:handle").unwrap();
1251
1430
1252
1431
assert!(all.grants(&handle));
1253
1432
assert!(all.grants(&all));
···
1257
1436
1258
1437
#[test]
1259
1438
fn test_blob_scope_grants() {
1260
1260
-
let all = Scope::parse("blob:*/*").unwrap();
1261
1261
-
let image_all = Scope::parse("blob:image/*").unwrap();
1262
1262
-
let image_png = Scope::parse("blob:image/png").unwrap();
1263
1263
-
let text_plain = Scope::parse("blob:text/plain").unwrap();
1439
1439
+
let all: Scope = Scope::parse("blob:*/*").unwrap();
1440
1440
+
let image_all: Scope = Scope::parse("blob:image/*").unwrap();
1441
1441
+
let image_png: Scope = Scope::parse("blob:image/png").unwrap();
1442
1442
+
let text_plain: Scope = Scope::parse("blob:text/plain").unwrap();
1264
1443
1265
1444
assert!(all.grants(&image_all));
1266
1445
assert!(all.grants(&image_png));
···
1272
1451
1273
1452
#[test]
1274
1453
fn test_repo_scope_grants() {
1275
1275
-
let all_all = Scope::parse("repo:*").unwrap();
1276
1276
-
let all_create = Scope::parse("repo:*?action=create").unwrap();
1277
1277
-
let specific_all = Scope::parse("repo:app.bsky.feed.post").unwrap();
1278
1278
-
let specific_create = Scope::parse("repo:app.bsky.feed.post?action=create").unwrap();
1279
1279
-
let other_create = Scope::parse("repo:pub.leaflet.publication?action=create").unwrap();
1454
1454
+
let all_all: Scope = Scope::parse("repo:*").unwrap();
1455
1455
+
let all_create: Scope = Scope::parse("repo:*?action=create").unwrap();
1456
1456
+
let specific_all: Scope = Scope::parse("repo:app.bsky.feed.post").unwrap();
1457
1457
+
let specific_create: Scope = Scope::parse("repo:app.bsky.feed.post?action=create").unwrap();
1458
1458
+
let other_create: Scope =
1459
1459
+
Scope::parse("repo:pub.leaflet.publication?action=create").unwrap();
1280
1460
1281
1461
assert!(all_all.grants(&all_create));
1282
1462
assert!(all_all.grants(&specific_all));
···
1290
1470
1291
1471
#[test]
1292
1472
fn test_rpc_scope_grants() {
1293
1293
-
let all = Scope::parse("rpc:*").unwrap();
1294
1294
-
let specific_lxm = Scope::parse("rpc:com.example.service").unwrap();
1295
1295
-
let specific_both = Scope::parse("rpc:com.example.service?aud=did:example:123").unwrap();
1473
1473
+
let all: Scope = Scope::parse("rpc:*").unwrap();
1474
1474
+
let specific_lxm: Scope = Scope::parse("rpc:com.example.service").unwrap();
1475
1475
+
let specific_both: Scope =
1476
1476
+
Scope::parse("rpc:com.example.service?aud=did:example:123").unwrap();
1296
1477
1297
1478
assert!(all.grants(&specific_lxm));
1298
1479
assert!(all.grants(&specific_both));
···
1303
1484
1304
1485
#[test]
1305
1486
fn test_cross_scope_grants() {
1306
1306
-
let account = Scope::parse("account:email").unwrap();
1307
1307
-
let identity = Scope::parse("identity:handle").unwrap();
1487
1487
+
let account: Scope = Scope::parse("account:email").unwrap();
1488
1488
+
let identity: Scope = Scope::parse("identity:handle").unwrap();
1308
1489
1309
1490
assert!(!account.grants(&identity));
1310
1491
assert!(!identity.grants(&account));
···
1313
1494
#[test]
1314
1495
fn test_parse_errors() {
1315
1496
assert!(matches!(
1316
1316
-
Scope::parse("unknown:test"),
1497
1497
+
Scope::<SmolStr>::parse("unknown:test"),
1317
1498
Err(ParseError::UnknownPrefix(_))
1318
1499
));
1319
1500
1320
1501
assert!(matches!(
1321
1321
-
Scope::parse("account"),
1502
1502
+
Scope::<SmolStr>::parse("account"),
1322
1503
Err(ParseError::MissingResource)
1323
1504
));
1324
1505
1325
1506
assert!(matches!(
1326
1326
-
Scope::parse("account:invalid"),
1507
1507
+
Scope::<SmolStr>::parse("account:invalid"),
1327
1508
Err(ParseError::InvalidResource(_))
1328
1509
));
1329
1510
1330
1511
assert!(matches!(
1331
1331
-
Scope::parse("account:email?action=invalid"),
1512
1512
+
Scope::<SmolStr>::parse("account:email?action=invalid"),
1332
1513
Err(ParseError::InvalidAction(_))
1333
1514
));
1334
1515
}
1335
1516
1336
1517
#[test]
1337
1518
fn test_query_parameter_sorting() {
1338
1338
-
let scope =
1339
1339
-
Scope::parse("blob?accept=image/png&accept=application/pdf&accept=image/jpeg").unwrap();
1519
1519
+
let scope = Scope::<SmolStr>::parse(
1520
1520
+
"blob?accept=image/png&accept=application/pdf&accept=image/jpeg",
1521
1521
+
)
1522
1522
+
.unwrap();
1340
1523
let normalized = scope.to_string_normalized();
1341
1524
assert!(normalized.contains("accept=application/pdf"));
1342
1525
assert!(normalized.contains("accept=image/jpeg"));
···
1350
1533
1351
1534
#[test]
1352
1535
fn test_repo_action_wildcard() {
1353
1353
-
let scope = Scope::parse("repo:app.bsky.feed.post?action=*").unwrap();
1536
1536
+
let scope = Scope::<SmolStr>::parse("repo:app.bsky.feed.post?action=*").unwrap();
1354
1537
let mut actions = BTreeSet::new();
1355
1538
actions.insert(RepoAction::Create);
1356
1539
actions.insert(RepoAction::Update);
···
1358
1541
assert_eq!(
1359
1542
scope,
1360
1543
Scope::Repo(RepoScope {
1361
1361
-
collection: RepoCollection::Nsid(Nsid::new_static("app.bsky.feed.post").unwrap()),
1544
1544
+
collection: RepoCollection::Nsid(Nsid::new_owned("app.bsky.feed.post").unwrap()),
1362
1545
actions,
1363
1546
})
1364
1547
);
···
1366
1549
1367
1550
#[test]
1368
1551
fn test_multiple_blob_accepts() {
1369
1369
-
let scope = Scope::parse("blob?accept=image/*&accept=text/plain").unwrap();
1370
1370
-
assert!(scope.grants(&Scope::parse("blob:image/png").unwrap()));
1371
1371
-
assert!(scope.grants(&Scope::parse("blob:text/plain").unwrap()));
1372
1372
-
assert!(!scope.grants(&Scope::parse("blob:application/json").unwrap()));
1552
1552
+
let scope = Scope::<SmolStr>::parse("blob?accept=image/*&accept=text/plain").unwrap();
1553
1553
+
assert!(scope.grants(&Scope::<SmolStr>::parse("blob:image/png").unwrap()));
1554
1554
+
assert!(scope.grants(&Scope::<SmolStr>::parse("blob:text/plain").unwrap()));
1555
1555
+
assert!(!scope.grants(&Scope::<SmolStr>::parse("blob:application/json").unwrap()));
1373
1556
}
1374
1557
1375
1558
#[test]
1376
1559
fn test_rpc_default_wildcards() {
1377
1377
-
let scope = Scope::parse("rpc").unwrap();
1560
1560
+
let scope = Scope::<SmolStr>::parse("rpc").unwrap();
1378
1561
let mut lxm = BTreeSet::new();
1379
1562
let mut aud = BTreeSet::new();
1380
1563
lxm.insert(RpcLexicon::All);
···
1384
1567
1385
1568
#[test]
1386
1569
fn test_atproto_scope_parsing() {
1387
1387
-
let scope = Scope::parse("atproto").unwrap();
1570
1570
+
let scope = Scope::<SmolStr>::parse("atproto").unwrap();
1388
1571
assert_eq!(scope, Scope::Atproto);
1389
1572
1390
1573
// Atproto should not accept suffixes
1391
1391
-
assert!(Scope::parse("atproto:something").is_err());
1392
1392
-
assert!(Scope::parse("atproto?param=value").is_err());
1574
1574
+
assert!(Scope::<SmolStr>::parse("atproto:something").is_err());
1575
1575
+
assert!(Scope::<SmolStr>::parse("atproto?param=value").is_err());
1393
1576
}
1394
1577
1395
1578
#[test]
1396
1579
fn test_transition_scope_parsing() {
1397
1397
-
let scope = Scope::parse("transition:generic").unwrap();
1580
1580
+
let scope = Scope::<SmolStr>::parse("transition:generic").unwrap();
1398
1581
assert_eq!(scope, Scope::Transition(TransitionScope::Generic));
1399
1582
1400
1400
-
let scope = Scope::parse("transition:email").unwrap();
1583
1583
+
let scope = Scope::<SmolStr>::parse("transition:email").unwrap();
1401
1584
assert_eq!(scope, Scope::Transition(TransitionScope::Email));
1402
1585
1403
1586
// Test invalid transition types
1404
1587
assert!(matches!(
1405
1405
-
Scope::parse("transition:invalid"),
1588
1588
+
Scope::<SmolStr>::parse("transition:invalid"),
1406
1589
Err(ParseError::InvalidResource(_))
1407
1590
));
1408
1591
1409
1592
// Test missing suffix
1410
1593
assert!(matches!(
1411
1411
-
Scope::parse("transition"),
1594
1594
+
Scope::<SmolStr>::parse("transition"),
1412
1595
Err(ParseError::MissingResource)
1413
1596
));
1414
1597
1415
1598
// Test transition doesn't accept query parameters
1416
1599
assert!(matches!(
1417
1417
-
Scope::parse("transition:generic?param=value"),
1600
1600
+
Scope::<SmolStr>::parse("transition:generic?param=value"),
1418
1601
Err(ParseError::InvalidResource(_))
1419
1602
));
1420
1603
}
1421
1604
1422
1605
#[test]
1423
1606
fn test_atproto_scope_normalization() {
1424
1424
-
let scope = Scope::parse("atproto").unwrap();
1607
1607
+
let scope = Scope::<SmolStr>::parse("atproto").unwrap();
1425
1608
assert_eq!(scope.to_string_normalized(), "atproto");
1426
1609
}
1427
1610
···
1433
1616
];
1434
1617
1435
1618
for (input, expected) in tests {
1436
1436
-
let scope = Scope::parse(input).unwrap();
1619
1619
+
let scope = Scope::<SmolStr>::parse(input).unwrap();
1437
1620
assert_eq!(scope.to_string_normalized(), expected);
1438
1621
}
1439
1622
}
1440
1623
1441
1624
#[test]
1442
1625
fn test_atproto_scope_grants() {
1443
1443
-
let atproto = Scope::parse("atproto").unwrap();
1444
1444
-
let account = Scope::parse("account:email").unwrap();
1445
1445
-
let identity = Scope::parse("identity:handle").unwrap();
1446
1446
-
let blob = Scope::parse("blob:image/png").unwrap();
1447
1447
-
let repo = Scope::parse("repo:app.bsky.feed.post").unwrap();
1448
1448
-
let rpc = Scope::parse("rpc:com.example.service").unwrap();
1449
1449
-
let transition_generic = Scope::parse("transition:generic").unwrap();
1450
1450
-
let transition_email = Scope::parse("transition:email").unwrap();
1626
1626
+
let atproto = Scope::<SmolStr>::parse("atproto").unwrap();
1627
1627
+
let account = Scope::<SmolStr>::parse("account:email").unwrap();
1628
1628
+
let identity = Scope::<SmolStr>::parse("identity:handle").unwrap();
1629
1629
+
let blob = Scope::<SmolStr>::parse("blob:image/png").unwrap();
1630
1630
+
let repo = Scope::<SmolStr>::parse("repo:app.bsky.feed.post").unwrap();
1631
1631
+
let rpc = Scope::<SmolStr>::parse("rpc:com.example.service").unwrap();
1632
1632
+
let transition_generic = Scope::<SmolStr>::parse("transition:generic").unwrap();
1633
1633
+
let transition_email = Scope::<SmolStr>::parse("transition:email").unwrap();
1451
1634
1452
1635
// Atproto only grants itself (it's a required scope, not a permission grant)
1453
1636
assert!(atproto.grants(&atproto));
···
1471
1654
1472
1655
#[test]
1473
1656
fn test_transition_scope_grants() {
1474
1474
-
let transition_generic = Scope::parse("transition:generic").unwrap();
1475
1475
-
let transition_email = Scope::parse("transition:email").unwrap();
1476
1476
-
let account = Scope::parse("account:email").unwrap();
1657
1657
+
let transition_generic = Scope::<SmolStr>::parse("transition:generic").unwrap();
1658
1658
+
let transition_email = Scope::<SmolStr>::parse("transition:email").unwrap();
1659
1659
+
let account = Scope::<SmolStr>::parse("account:email").unwrap();
1477
1660
1478
1661
// Transition scopes only grant themselves
1479
1662
assert!(transition_generic.grants(&transition_generic));
···
1493
1676
#[test]
1494
1677
fn test_parse_multiple() {
1495
1678
// Test parsing multiple scopes
1496
1496
-
let scopes = Scope::parse_multiple("atproto repo:*").unwrap();
1679
1679
+
let scopes = Scope::<SmolStr>::parse_multiple("atproto repo:*").unwrap();
1497
1680
assert_eq!(scopes.len(), 2);
1498
1681
assert_eq!(scopes[0], Scope::Atproto);
1499
1682
assert_eq!(
···
1511
1694
);
1512
1695
1513
1696
// Test with more scopes
1514
1514
-
let scopes = Scope::parse_multiple("account:email identity:handle blob:image/png").unwrap();
1697
1697
+
let scopes =
1698
1698
+
Scope::<SmolStr>::parse_multiple("account:email identity:handle blob:image/png")
1699
1699
+
.unwrap();
1515
1700
assert_eq!(scopes.len(), 3);
1516
1701
assert!(matches!(scopes[0], Scope::Account(_)));
1517
1702
assert!(matches!(scopes[1], Scope::Identity(_)));
1518
1703
assert!(matches!(scopes[2], Scope::Blob(_)));
1519
1704
1520
1705
// Test with complex scopes
1521
1521
-
let scopes = Scope::parse_multiple(
1706
1706
+
let scopes = Scope::<SmolStr>::parse_multiple(
1522
1707
"account:email?action=manage repo:app.bsky.feed.post?action=create transition:email",
1523
1708
)
1524
1709
.unwrap();
1525
1710
assert_eq!(scopes.len(), 3);
1526
1711
1527
1712
// Test empty string
1528
1528
-
let scopes = Scope::parse_multiple("").unwrap();
1713
1713
+
let scopes = Scope::<SmolStr>::parse_multiple("").unwrap();
1529
1714
assert_eq!(scopes.len(), 0);
1530
1715
1531
1716
// Test whitespace only
1532
1532
-
let scopes = Scope::parse_multiple(" ").unwrap();
1717
1717
+
let scopes = Scope::<SmolStr>::parse_multiple(" ").unwrap();
1533
1718
assert_eq!(scopes.len(), 0);
1534
1719
1535
1720
// Test with extra whitespace
1536
1536
-
let scopes = Scope::parse_multiple(" atproto repo:* ").unwrap();
1721
1721
+
let scopes = Scope::<SmolStr>::parse_multiple(" atproto repo:* ").unwrap();
1537
1722
assert_eq!(scopes.len(), 2);
1538
1723
1539
1724
// Test single scope
1540
1540
-
let scopes = Scope::parse_multiple("atproto").unwrap();
1725
1725
+
let scopes = Scope::<SmolStr>::parse_multiple("atproto").unwrap();
1541
1726
assert_eq!(scopes.len(), 1);
1542
1727
assert_eq!(scopes[0], Scope::Atproto);
1543
1728
1544
1729
// Test error propagation
1545
1545
-
assert!(Scope::parse_multiple("atproto invalid:scope").is_err());
1546
1546
-
assert!(Scope::parse_multiple("account:invalid repo:*").is_err());
1730
1730
+
assert!(Scope::<SmolStr>::parse_multiple("atproto invalid:scope").is_err());
1731
1731
+
assert!(Scope::<SmolStr>::parse_multiple("account:invalid repo:*").is_err());
1547
1732
}
1548
1733
1549
1734
#[test]
1550
1735
fn test_parse_multiple_reduced() {
1551
1736
// Test repo scope reduction - wildcard grants specific
1552
1737
let scopes =
1553
1553
-
Scope::parse_multiple_reduced("atproto repo:app.bsky.feed.post repo:*").unwrap();
1738
1738
+
Scope::<SmolStr>::parse_multiple_reduced("atproto repo:app.bsky.feed.post repo:*")
1739
1739
+
.unwrap();
1554
1740
assert_eq!(scopes.len(), 2);
1555
1741
assert!(scopes.contains(&Scope::Atproto));
1556
1742
assert!(scopes.contains(&Scope::Repo(RepoScope {
···
1566
1752
1567
1753
// Test reverse order - should get same result
1568
1754
let scopes =
1569
1569
-
Scope::parse_multiple_reduced("atproto repo:* repo:app.bsky.feed.post").unwrap();
1755
1755
+
Scope::<SmolStr>::parse_multiple_reduced("atproto repo:* repo:app.bsky.feed.post")
1756
1756
+
.unwrap();
1570
1757
assert_eq!(scopes.len(), 2);
1571
1758
assert!(scopes.contains(&Scope::Atproto));
1572
1759
assert!(scopes.contains(&Scope::Repo(RepoScope {
···
1582
1769
1583
1770
// Test account scope reduction - manage grants read
1584
1771
let scopes =
1585
1585
-
Scope::parse_multiple_reduced("account:email account:email?action=manage").unwrap();
1772
1772
+
Scope::<SmolStr>::parse_multiple_reduced("account:email account:email?action=manage")
1773
1773
+
.unwrap();
1586
1774
assert_eq!(scopes.len(), 1);
1587
1775
assert_eq!(
1588
1776
scopes[0],
···
1593
1781
);
1594
1782
1595
1783
// Test identity scope reduction - wildcard grants specific
1596
1596
-
let scopes = Scope::parse_multiple_reduced("identity:handle identity:*").unwrap();
1784
1784
+
let scopes =
1785
1785
+
Scope::<SmolStr>::parse_multiple_reduced("identity:handle identity:*").unwrap();
1597
1786
assert_eq!(scopes.len(), 1);
1598
1787
assert_eq!(scopes[0], Scope::Identity(IdentityScope::All));
1599
1788
1600
1789
// Test blob scope reduction - wildcard grants specific
1601
1601
-
let scopes = Scope::parse_multiple_reduced("blob:image/png blob:image/* blob:*/*").unwrap();
1790
1790
+
let scopes =
1791
1791
+
Scope::<SmolStr>::parse_multiple_reduced("blob:image/png blob:image/* blob:*/*")
1792
1792
+
.unwrap();
1602
1793
assert_eq!(scopes.len(), 1);
1603
1794
let mut accept = BTreeSet::new();
1604
1795
accept.insert(MimePattern::All);
1605
1796
assert_eq!(scopes[0], Scope::Blob(BlobScope { accept }));
1606
1797
1607
1798
// Test no reduction needed - different scope types
1608
1608
-
let scopes =
1609
1609
-
Scope::parse_multiple_reduced("account:email identity:handle blob:image/png").unwrap();
1799
1799
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced(
1800
1800
+
"account:email identity:handle blob:image/png",
1801
1801
+
)
1802
1802
+
.unwrap();
1610
1803
assert_eq!(scopes.len(), 3);
1611
1804
1612
1805
// Test repo action reduction
1613
1613
-
let scopes = Scope::parse_multiple_reduced(
1806
1806
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced(
1614
1807
"repo:app.bsky.feed.post?action=create repo:app.bsky.feed.post",
1615
1808
)
1616
1809
.unwrap();
···
1618
1811
assert_eq!(
1619
1812
scopes[0],
1620
1813
Scope::Repo(RepoScope {
1621
1621
-
collection: RepoCollection::Nsid(Nsid::new_static("app.bsky.feed.post").unwrap()),
1814
1814
+
collection: RepoCollection::Nsid(Nsid::new_owned("app.bsky.feed.post").unwrap()),
1622
1815
actions: {
1623
1816
let mut actions = BTreeSet::new();
1624
1817
actions.insert(RepoAction::Create);
···
1630
1823
);
1631
1824
1632
1825
// Test RPC scope reduction
1633
1633
-
let scopes = Scope::parse_multiple_reduced(
1826
1826
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced(
1634
1827
"rpc:com.example.service?aud=did:example:123 rpc:com.example.service rpc:*",
1635
1828
)
1636
1829
.unwrap();
···
1652
1845
);
1653
1846
1654
1847
// Test duplicate removal
1655
1655
-
let scopes = Scope::parse_multiple_reduced("atproto atproto atproto").unwrap();
1848
1848
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced("atproto atproto atproto").unwrap();
1656
1849
assert_eq!(scopes.len(), 1);
1657
1850
assert_eq!(scopes[0], Scope::Atproto);
1658
1851
1659
1852
// Test transition scopes - only grant themselves
1660
1660
-
let scopes = Scope::parse_multiple_reduced("transition:generic transition:email").unwrap();
1853
1853
+
let scopes =
1854
1854
+
Scope::<SmolStr>::parse_multiple_reduced("transition:generic transition:email")
1855
1855
+
.unwrap();
1661
1856
assert_eq!(scopes.len(), 2);
1662
1857
assert!(scopes.contains(&Scope::Transition(TransitionScope::Generic)));
1663
1858
assert!(scopes.contains(&Scope::Transition(TransitionScope::Email)));
1664
1859
1665
1860
// Test empty input
1666
1666
-
let scopes = Scope::parse_multiple_reduced("").unwrap();
1861
1861
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced("").unwrap();
1667
1862
assert_eq!(scopes.len(), 0);
1668
1863
1669
1864
// Test complex scenario with multiple reductions
1670
1670
-
let scopes = Scope::parse_multiple_reduced(
1865
1865
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced(
1671
1866
"account:email?action=manage account:email account:repo account:repo?action=read identity:* identity:handle"
1672
1867
).unwrap();
1673
1868
assert_eq!(scopes.len(), 3);
···
1683
1878
assert!(scopes.contains(&Scope::Identity(IdentityScope::All)));
1684
1879
1685
1880
// Test that atproto doesn't grant other scopes (per recent change)
1686
1686
-
let scopes = Scope::parse_multiple_reduced("atproto account:email repo:*").unwrap();
1881
1881
+
let scopes =
1882
1882
+
Scope::<SmolStr>::parse_multiple_reduced("atproto account:email repo:*").unwrap();
1687
1883
assert_eq!(scopes.len(), 3);
1688
1884
assert!(scopes.contains(&Scope::Atproto));
1689
1885
assert!(scopes.contains(&Scope::Account(AccountScope {
···
1705
1901
#[test]
1706
1902
fn test_openid_connect_scope_parsing() {
1707
1903
// Test OpenID scope
1708
1708
-
let scope = Scope::parse("openid").unwrap();
1904
1904
+
let scope = Scope::<SmolStr>::parse("openid").unwrap();
1709
1905
assert_eq!(scope, Scope::OpenId);
1710
1906
1711
1907
// Test Profile scope
1712
1712
-
let scope = Scope::parse("profile").unwrap();
1908
1908
+
let scope = Scope::<SmolStr>::parse("profile").unwrap();
1713
1909
assert_eq!(scope, Scope::Profile);
1714
1910
1715
1911
// Test Email scope
1716
1716
-
let scope = Scope::parse("email").unwrap();
1912
1912
+
let scope = Scope::<SmolStr>::parse("email").unwrap();
1717
1913
assert_eq!(scope, Scope::Email);
1718
1914
1719
1915
// Test that they don't accept suffixes
1720
1720
-
assert!(Scope::parse("openid:something").is_err());
1721
1721
-
assert!(Scope::parse("profile:something").is_err());
1722
1722
-
assert!(Scope::parse("email:something").is_err());
1916
1916
+
assert!(Scope::<SmolStr>::parse("openid:something").is_err());
1917
1917
+
assert!(Scope::<SmolStr>::parse("profile:something").is_err());
1918
1918
+
assert!(Scope::<SmolStr>::parse("email:something").is_err());
1723
1919
1724
1920
// Test that they don't accept query parameters
1725
1725
-
assert!(Scope::parse("openid?param=value").is_err());
1726
1726
-
assert!(Scope::parse("profile?param=value").is_err());
1727
1727
-
assert!(Scope::parse("email?param=value").is_err());
1921
1921
+
assert!(Scope::<SmolStr>::parse("openid?param=value").is_err());
1922
1922
+
assert!(Scope::<SmolStr>::parse("profile?param=value").is_err());
1923
1923
+
assert!(Scope::<SmolStr>::parse("email?param=value").is_err());
1728
1924
}
1729
1925
1730
1926
#[test]
1731
1927
fn test_openid_connect_scope_normalization() {
1732
1732
-
let scope = Scope::parse("openid").unwrap();
1928
1928
+
let scope = Scope::<SmolStr>::parse("openid").unwrap();
1733
1929
assert_eq!(scope.to_string_normalized(), "openid");
1734
1930
1735
1735
-
let scope = Scope::parse("profile").unwrap();
1931
1931
+
let scope = Scope::<SmolStr>::parse("profile").unwrap();
1736
1932
assert_eq!(scope.to_string_normalized(), "profile");
1737
1933
1738
1738
-
let scope = Scope::parse("email").unwrap();
1934
1934
+
let scope = Scope::<SmolStr>::parse("email").unwrap();
1739
1935
assert_eq!(scope.to_string_normalized(), "email");
1740
1936
}
1741
1937
1742
1938
#[test]
1743
1939
fn test_openid_connect_scope_grants() {
1744
1744
-
let openid = Scope::parse("openid").unwrap();
1745
1745
-
let profile = Scope::parse("profile").unwrap();
1746
1746
-
let email = Scope::parse("email").unwrap();
1747
1747
-
let account = Scope::parse("account:email").unwrap();
1940
1940
+
let openid = Scope::<SmolStr>::parse("openid").unwrap();
1941
1941
+
let profile = Scope::<SmolStr>::parse("profile").unwrap();
1942
1942
+
let email = Scope::<SmolStr>::parse("email").unwrap();
1943
1943
+
let account = Scope::<SmolStr>::parse("account:email").unwrap();
1748
1944
1749
1945
// OpenID Connect scopes only grant themselves
1750
1946
assert!(openid.grants(&openid));
···
1770
1966
1771
1967
#[test]
1772
1968
fn test_parse_multiple_with_openid_connect() {
1773
1773
-
let scopes = Scope::parse_multiple("openid profile email atproto").unwrap();
1969
1969
+
let scopes = Scope::<SmolStr>::parse_multiple("openid profile email atproto").unwrap();
1774
1970
assert_eq!(scopes.len(), 4);
1775
1971
assert_eq!(scopes[0], Scope::OpenId);
1776
1972
assert_eq!(scopes[1], Scope::Profile);
···
1778
1974
assert_eq!(scopes[3], Scope::Atproto);
1779
1975
1780
1976
// Test with mixed scopes
1781
1781
-
let scopes = Scope::parse_multiple("openid account:email profile repo:*").unwrap();
1977
1977
+
let scopes =
1978
1978
+
Scope::<SmolStr>::parse_multiple("openid account:email profile repo:*").unwrap();
1782
1979
assert_eq!(scopes.len(), 4);
1783
1980
assert!(scopes.contains(&Scope::OpenId));
1784
1981
assert!(scopes.contains(&Scope::Profile));
···
1787
1984
#[test]
1788
1985
fn test_parse_multiple_reduced_with_openid_connect() {
1789
1986
// OpenID Connect scopes don't grant each other, so no reduction
1790
1790
-
let scopes = Scope::parse_multiple_reduced("openid profile email openid").unwrap();
1987
1987
+
let scopes =
1988
1988
+
Scope::<SmolStr>::parse_multiple_reduced("openid profile email openid").unwrap();
1791
1989
assert_eq!(scopes.len(), 3);
1792
1990
assert!(scopes.contains(&Scope::OpenId));
1793
1991
assert!(scopes.contains(&Scope::Profile));
1794
1992
assert!(scopes.contains(&Scope::Email));
1795
1993
1796
1994
// Mixed with other scopes
1797
1797
-
let scopes = Scope::parse_multiple_reduced(
1995
1995
+
let scopes = Scope::<SmolStr>::parse_multiple_reduced(
1798
1996
"openid account:email account:email?action=manage profile",
1799
1997
)
1800
1998
.unwrap();
···
1815
2013
1816
2014
// Test single scope
1817
2015
let scopes = vec![Scope::Atproto];
1818
1818
-
assert_eq!(Scope::serialize_multiple(&scopes), "atproto");
2016
2016
+
assert_eq!(Scope::<SmolStr>::serialize_multiple(&scopes), "atproto");
1819
2017
1820
2018
// Test multiple scopes - should be sorted alphabetically
1821
2019
let scopes = vec![
1822
1822
-
Scope::parse("repo:*").unwrap(),
2020
2020
+
Scope::<SmolStr>::parse("repo:*").unwrap(),
1823
2021
Scope::Atproto,
1824
2022
Scope::parse("account:email").unwrap(),
1825
2023
];
···
1830
2028
1831
2029
// Test that sorting is consistent regardless of input order
1832
2030
let scopes = vec![
1833
1833
-
Scope::parse("identity:handle").unwrap(),
2031
2031
+
Scope::<SmolStr>::parse("identity:handle").unwrap(),
1834
2032
Scope::parse("blob:image/png").unwrap(),
1835
2033
Scope::parse("account:repo?action=manage").unwrap(),
1836
2034
];
···
1842
2040
// Test with OpenID Connect scopes
1843
2041
let scopes = vec![Scope::Email, Scope::OpenId, Scope::Profile, Scope::Atproto];
1844
2042
assert_eq!(
1845
1845
-
Scope::serialize_multiple(&scopes),
2043
2043
+
Scope::<SmolStr>::serialize_multiple(&scopes),
1846
2044
"atproto email openid profile"
1847
2045
);
1848
2046
1849
2047
// Test with complex scopes including query parameters
1850
2048
let scopes = vec![
1851
1851
-
Scope::parse("rpc:com.example.service?aud=did:plc:yfvwmnlztr4dwkb7hwz55r2g&lxm=com.example.method")
2049
2049
+
Scope::<SmolStr>::parse("rpc:com.example.service?aud=did:plc:yfvwmnlztr4dwkb7hwz55r2g&lxm=com.example.method")
1852
2050
.unwrap(),
1853
2051
Scope::parse("repo:app.bsky.feed.post?action=create&action=update").unwrap(),
1854
2052
Scope::parse("blob:image/*?accept=image/png&accept=image/jpeg").unwrap(),
···
1869
2067
Scope::Atproto,
1870
2068
];
1871
2069
assert_eq!(
1872
1872
-
Scope::serialize_multiple(&scopes),
2070
2070
+
Scope::<&str>::serialize_multiple(&scopes),
1873
2071
"atproto transition:email transition:generic"
1874
2072
);
1875
2073
···
1877
2075
let scopes = vec![
1878
2076
Scope::Atproto,
1879
2077
Scope::Atproto,
1880
1880
-
Scope::parse("account:email").unwrap(),
2078
2078
+
Scope::<SmolStr>::parse("account:email").unwrap(),
1881
2079
];
1882
2080
assert_eq!(
1883
2081
Scope::serialize_multiple(&scopes),
···
1885
2083
);
1886
2084
1887
2085
// Test normalization is preserved in serialization
1888
1888
-
let scopes = vec![Scope::parse("blob?accept=image/png&accept=image/jpeg").unwrap()];
2086
2086
+
let scopes =
2087
2087
+
vec![Scope::<SmolStr>::parse("blob?accept=image/png&accept=image/jpeg").unwrap()];
1889
2088
// Should normalize query parameters alphabetically
1890
2089
assert_eq!(
1891
2090
Scope::serialize_multiple(&scopes),
···
1897
2096
fn test_serialize_multiple_roundtrip() {
1898
2097
// Test that parse_multiple and serialize_multiple are inverses (when sorted)
1899
2098
let original = "account:email atproto blob:image/png identity:handle repo:*";
1900
1900
-
let scopes = Scope::parse_multiple(original).unwrap();
2099
2099
+
let scopes = Scope::<SmolStr>::parse_multiple(original).unwrap();
1901
2100
let serialized = Scope::serialize_multiple(&scopes);
1902
2101
assert_eq!(serialized, original);
1903
2102
1904
2103
// Test with complex scopes
1905
2104
let original = "account:repo?action=manage blob?accept=image/jpeg&accept=image/png rpc:*";
1906
1906
-
let scopes = Scope::parse_multiple(original).unwrap();
2105
2105
+
let scopes = Scope::<SmolStr>::parse_multiple(original).unwrap();
1907
2106
let serialized = Scope::serialize_multiple(&scopes);
1908
2107
// Parse again to verify it's valid
1909
2108
let reparsed = Scope::parse_multiple(&serialized).unwrap();
···
1911
2110
1912
2111
// Test with OpenID Connect scopes
1913
2112
let original = "email openid profile";
1914
1914
-
let scopes = Scope::parse_multiple(original).unwrap();
2113
2113
+
let scopes = Scope::<SmolStr>::parse_multiple(original).unwrap();
1915
2114
let serialized = Scope::serialize_multiple(&scopes);
1916
2115
assert_eq!(serialized, original);
1917
2116
}
···
1920
2119
fn test_remove_scope() {
1921
2120
// Test removing a scope that exists
1922
2121
let scopes = vec![
1923
1923
-
Scope::parse("repo:*").unwrap(),
2122
2122
+
Scope::<SmolStr>::parse("repo:*").unwrap(),
1924
2123
Scope::Atproto,
1925
2124
Scope::parse("account:email").unwrap(),
1926
2125
];
···
1933
2132
1934
2133
// Test removing a scope that doesn't exist
1935
2134
let scopes = vec![
1936
1936
-
Scope::parse("repo:*").unwrap(),
2135
2135
+
Scope::<SmolStr>::parse("repo:*").unwrap(),
1937
2136
Scope::parse("account:email").unwrap(),
1938
2137
];
1939
2138
let to_remove = Scope::parse("identity:handle").unwrap();
···
1950
2149
// Test removing all instances of a duplicate scope
1951
2150
let scopes = vec![
1952
2151
Scope::Atproto,
1953
1953
-
Scope::parse("account:email").unwrap(),
2152
2152
+
Scope::<SmolStr>::parse("account:email").unwrap(),
1954
2153
Scope::Atproto,
1955
2154
Scope::parse("repo:*").unwrap(),
1956
2155
Scope::Atproto,
···
1964
2163
1965
2164
// Test removing complex scopes with query parameters
1966
2165
let scopes = vec![
1967
1967
-
Scope::parse("account:email?action=manage").unwrap(),
2166
2166
+
Scope::<SmolStr>::parse("account:email?action=manage").unwrap(),
1968
2167
Scope::parse("blob?accept=image/png&accept=image/jpeg").unwrap(),
1969
2168
Scope::parse("rpc:com.example.service?aud=did:example:123").unwrap(),
1970
2169
];
···
1976
2175
// Test with OpenID Connect scopes
1977
2176
let scopes = vec![Scope::OpenId, Scope::Profile, Scope::Email, Scope::Atproto];
1978
2177
let to_remove = Scope::Profile;
1979
1979
-
let result = Scope::remove_scope(&scopes, &to_remove);
2178
2178
+
let result = Scope::<&str>::remove_scope(&scopes, &to_remove);
1980
2179
assert_eq!(result.len(), 3);
1981
2180
assert!(!result.contains(&to_remove));
1982
2181
assert!(result.contains(&Scope::OpenId));
···
1990
2189
Scope::Atproto,
1991
2190
];
1992
2191
let to_remove = Scope::Transition(TransitionScope::Email);
1993
1993
-
let result = Scope::remove_scope(&scopes, &to_remove);
2192
2192
+
let result = Scope::<&str>::remove_scope(&scopes, &to_remove);
1994
2193
assert_eq!(result.len(), 2);
1995
2194
assert!(!result.contains(&to_remove));
1996
2195
assert!(result.contains(&Scope::Transition(TransitionScope::Generic)));
···
1998
2197
1999
2198
// Test that only exact matches are removed
2000
2199
let scopes = vec![
2001
2001
-
Scope::parse("account:email").unwrap(),
2200
2200
+
Scope::<SmolStr>::parse("account:email").unwrap(),
2002
2201
Scope::parse("account:email?action=manage").unwrap(),
2003
2202
Scope::parse("account:repo").unwrap(),
2004
2203
];
···
1
1
-
use std::sync::Arc;
1
1
+
use std::{str::FromStr, sync::Arc};
2
2
3
3
use chrono::TimeDelta;
4
4
···
15
15
16
16
use dashmap::DashMap;
17
17
use jacquard_common::{
18
18
-
CowStr, IntoStatic,
18
18
+
IntoStatic,
19
19
+
bos::{BosStr, DefaultStr},
19
20
deps::fluent_uri::Uri,
20
21
http_client::HttpClient,
21
22
session::SessionStoreError,
···
37
38
/// Return the private JWK used to sign DPoP proofs.
38
39
fn key(&self) -> &Key;
39
40
/// Return the most recently observed nonce from the authorization server, if any.
40
40
-
fn authserver_nonce(&self) -> Option<CowStr<'_>>;
41
41
+
fn authserver_nonce(&self) -> Option<&str>;
41
42
/// Persist a new nonce received from the authorization server.
42
42
-
fn set_authserver_nonce(&mut self, nonce: CowStr<'_>);
43
43
+
fn set_authserver_nonce(&mut self, nonce: SmolStr);
43
44
/// Return the most recently observed nonce from the resource server (PDS), if any.
44
44
-
fn host_nonce(&self) -> Option<CowStr<'_>>;
45
45
+
fn host_nonce(&self) -> Option<&str>;
45
46
/// Persist a new nonce received from the resource server (PDS).
46
46
-
fn set_host_nonce(&mut self, nonce: CowStr<'_>);
47
47
+
fn set_host_nonce(&mut self, nonce: SmolStr);
47
48
}
48
49
49
50
/// Persisted information about an OAuth session. Used to resume an active session.
50
51
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
51
51
-
pub struct ClientSessionData<'s> {
52
52
+
#[serde(bound(
53
53
+
serialize = "S: serde::Serialize + BosStr + Ord",
54
54
+
deserialize = "S: serde::Deserialize<'de> + BosStr, Scope<S>: serde::Deserialize<'de>"
55
55
+
))]
56
56
+
pub struct ClientSessionData<S: BosStr = DefaultStr> {
52
57
/// DID of the authenticated account; serves as the primary key for session storage
53
58
/// because only one active session per account is assumed.
54
54
-
#[serde(borrow)]
55
55
-
pub account_did: Did<'s>,
59
59
+
pub account_did: Did<S>,
56
60
57
61
/// Opaque identifier that distinguishes this session from other sessions for the same account.
58
62
///
59
63
/// Reuses the random `state` token generated during the PAR flow.
60
60
-
pub session_id: CowStr<'s>,
64
64
+
pub session_id: S,
61
65
62
66
/// Base URL of the resource server (PDS): scheme, host, and port only
63
67
pub host_url: Uri<String>,
64
68
65
69
/// Base URL of the authorization server (PDS or entryway): scheme, host, and port only
66
66
-
pub authserver_url: CowStr<'s>,
70
70
+
pub authserver_url: S,
67
71
68
72
/// Full URL of the authorization server's token endpoint.
69
69
-
pub authserver_token_endpoint: CowStr<'s>,
73
73
+
pub authserver_token_endpoint: S,
70
74
71
75
/// Full URL of the authorization server's revocation endpoint, if advertised.
72
76
#[serde(skip_serializing_if = "std::option::Option::is_none")]
73
73
-
pub authserver_revocation_endpoint: Option<CowStr<'s>>,
77
77
+
pub authserver_revocation_endpoint: Option<S>,
74
78
75
79
/// The set of OAuth scopes approved for this session, as returned in the initial token response.
76
76
-
pub scopes: Vec<Scope<'s>>,
80
80
+
pub scopes: Vec<Scope<S>>,
77
81
78
82
/// DPoP key and nonce state for ongoing requests in this session.
79
83
#[serde(flatten)]
80
80
-
pub dpop_data: DpopClientData<'s>,
84
84
+
pub dpop_data: DpopClientData,
81
85
82
86
/// Current token set (access token, refresh token, expiry, etc.).
83
87
#[serde(flatten)]
84
84
-
pub token_set: TokenSet<'s>,
88
88
+
pub token_set: TokenSet<S>,
85
89
}
86
90
87
87
-
impl IntoStatic for ClientSessionData<'_> {
88
88
-
type Output = ClientSessionData<'static>;
91
91
+
impl<S: BosStr + Ord + IntoStatic> IntoStatic for ClientSessionData<S>
92
92
+
where
93
93
+
S::Output: BosStr + Ord,
94
94
+
{
95
95
+
type Output = ClientSessionData<S::Output>;
89
96
90
97
fn into_static(self) -> Self::Output {
91
98
ClientSessionData {
···
95
102
.authserver_revocation_endpoint
96
103
.map(IntoStatic::into_static),
97
104
scopes: self.scopes.into_static(),
98
98
-
dpop_data: self.dpop_data.into_static(),
105
105
+
dpop_data: self.dpop_data,
99
106
token_set: self.token_set.into_static(),
100
107
account_did: self.account_did.into_static(),
101
108
session_id: self.session_id.into_static(),
···
104
111
}
105
112
}
106
113
107
107
-
impl ClientSessionData<'_> {
114
114
+
impl<S: BosStr + Ord> ClientSessionData<S> {
108
115
/// Update this session's token set and, if the new token set includes scopes, replace the scope list.
109
116
///
110
117
/// Called after a successful token refresh so that any scope changes returned by the server
111
118
/// are reflected in the persisted session without requiring a full re-authentication.
112
112
-
pub fn update_with_tokens(&mut self, token_set: TokenSet<'_>) {
119
119
+
///
120
120
+
/// This method is only available on `DefaultStr`-backed sessions (the common case for
121
121
+
/// in-memory sessions). Zero-copy borrowed sessions are read-only by nature and would
122
122
+
/// not be refreshed in place.
123
123
+
pub fn update_with_tokens(&mut self, token_set: &TokenSet<S>)
124
124
+
where
125
125
+
S: FromStr + Clone,
126
126
+
S::Err: std::fmt::Debug,
127
127
+
{
113
128
if let Some(Ok(scopes)) = token_set
114
129
.scope
115
130
.as_ref()
116
116
-
.map(|scope| Scope::parse_multiple_reduced(&scope).map(IntoStatic::into_static))
131
131
+
.map(|scope| Scope::<S>::parse_multiple_reduced(scope.as_ref()))
117
132
{
118
118
-
self.scopes = scopes;
133
133
+
self.scopes = scopes.into_iter().map(|s| s.convert()).collect();
119
134
}
120
120
-
self.token_set = token_set.into_static();
135
135
+
self.token_set = token_set.clone();
121
136
}
122
137
}
123
138
···
126
141
/// Both nonces must be written back to the store after each request so that the next
127
142
/// request to the same server includes the correct replay-protection nonce.
128
143
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
129
129
-
pub struct DpopClientData<'s> {
144
144
+
pub struct DpopClientData {
130
145
/// The private JWK bound to this session; used to sign all DPoP proofs.
131
146
pub dpop_key: Key,
132
147
/// Most recently observed DPoP nonce from the authorization server.
133
133
-
#[serde(borrow)]
134
134
-
pub dpop_authserver_nonce: CowStr<'s>,
148
148
+
pub dpop_authserver_nonce: SmolStr,
135
149
/// Most recently observed DPoP nonce from the resource server (PDS).
136
136
-
pub dpop_host_nonce: CowStr<'s>,
150
150
+
pub dpop_host_nonce: SmolStr,
137
151
}
138
152
139
139
-
impl IntoStatic for DpopClientData<'_> {
140
140
-
type Output = DpopClientData<'static>;
141
141
-
142
142
-
fn into_static(self) -> Self::Output {
143
143
-
DpopClientData {
144
144
-
dpop_key: self.dpop_key,
145
145
-
dpop_authserver_nonce: self.dpop_authserver_nonce.into_static(),
146
146
-
dpop_host_nonce: self.dpop_host_nonce.into_static(),
147
147
-
}
148
148
-
}
149
149
-
}
150
150
-
151
151
-
impl DpopDataSource for DpopClientData<'_> {
153
153
+
impl DpopDataSource for DpopClientData {
152
154
fn key(&self) -> &Key {
153
155
&self.dpop_key
154
156
}
155
155
-
fn authserver_nonce(&self) -> Option<CowStr<'_>> {
156
156
-
Some(self.dpop_authserver_nonce.clone())
157
157
+
158
158
+
fn authserver_nonce(&self) -> Option<&str> {
159
159
+
Some(self.dpop_authserver_nonce.as_ref())
157
160
}
158
161
159
159
-
fn host_nonce(&self) -> Option<CowStr<'_>> {
160
160
-
Some(self.dpop_host_nonce.clone())
162
162
+
fn host_nonce(&self) -> Option<&str> {
163
163
+
Some(self.dpop_host_nonce.as_ref())
161
164
}
162
165
163
163
-
fn set_authserver_nonce(&mut self, nonce: CowStr<'_>) {
164
164
-
self.dpop_authserver_nonce = nonce.into_static();
166
166
+
fn set_authserver_nonce(&mut self, nonce: SmolStr) {
167
167
+
self.dpop_authserver_nonce = nonce;
165
168
}
166
169
167
167
-
fn set_host_nonce(&mut self, nonce: CowStr<'_>) {
168
168
-
self.dpop_host_nonce = nonce.into_static();
170
170
+
fn set_host_nonce(&mut self, nonce: SmolStr) {
171
171
+
self.dpop_host_nonce = nonce;
169
172
}
170
173
}
171
174
···
175
178
/// [`crate::client::OAuthClient::callback`] so that the callback can verify the
176
179
/// `state`, reconstruct the token exchange, and create a full [`ClientSessionData`].
177
180
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
178
178
-
pub struct AuthRequestData<'s> {
181
181
+
#[serde(bound(
182
182
+
serialize = "S: serde::Serialize + BosStr + Ord",
183
183
+
deserialize = "S: serde::Deserialize<'de> + BosStr, Scope<S>: serde::Deserialize<'de>"
184
184
+
))]
185
185
+
pub struct AuthRequestData<S: BosStr = DefaultStr> {
179
186
/// Random identifier generated for this authorization request; used as the primary key
180
187
/// for storing and looking up this record during the callback.
181
181
-
#[serde(borrow)]
182
182
-
pub state: CowStr<'s>,
188
188
+
pub state: S,
183
189
184
190
/// Base URL of the authorization server that was selected for this flow.
185
185
-
pub authserver_url: CowStr<'s>,
191
191
+
pub authserver_url: S,
186
192
187
193
/// If the flow was initiated with a DID or handle, the resolved DID is stored here
188
194
/// so it can be compared against the `sub` in the token response.
189
195
#[serde(skip_serializing_if = "std::option::Option::is_none")]
190
190
-
pub account_did: Option<Did<'s>>,
196
196
+
pub account_did: Option<Did<S>>,
191
197
192
198
/// OAuth scopes requested for this authorization.
193
193
-
pub scopes: Vec<Scope<'s>>,
199
199
+
pub scopes: Vec<Scope<S>>,
194
200
195
201
/// The PAR `request_uri` returned by the authorization server; included in the redirect URL.
196
196
-
pub request_uri: CowStr<'s>,
202
202
+
pub request_uri: S,
197
203
198
204
/// Full URL of the authorization server's token endpoint.
199
199
-
pub authserver_token_endpoint: CowStr<'s>,
205
205
+
pub authserver_token_endpoint: S,
200
206
201
207
/// Full URL of the authorization server's revocation endpoint, if advertised.
202
208
#[serde(skip_serializing_if = "std::option::Option::is_none")]
203
203
-
pub authserver_revocation_endpoint: Option<CowStr<'s>>,
209
209
+
pub authserver_revocation_endpoint: Option<S>,
204
210
205
211
/// The PKCE code verifier whose SHA-256 hash was sent as the code challenge; required
206
212
/// at the token exchange step to prove the initiator of the auth request.
207
207
-
pub pkce_verifier: CowStr<'s>,
213
213
+
pub pkce_verifier: S,
208
214
209
215
/// DPoP key and any authserver nonce observed during the PAR request.
210
216
#[serde(flatten)]
211
211
-
pub dpop_data: DpopReqData<'s>,
217
217
+
pub dpop_data: DpopReqData,
212
218
}
213
219
214
214
-
impl IntoStatic for AuthRequestData<'_> {
215
215
-
type Output = AuthRequestData<'static>;
216
216
-
fn into_static(self) -> AuthRequestData<'static> {
220
220
+
impl<S: BosStr + Ord + IntoStatic> IntoStatic for AuthRequestData<S>
221
221
+
where
222
222
+
S::Output: BosStr + Ord,
223
223
+
{
224
224
+
type Output = AuthRequestData<S::Output>;
225
225
+
226
226
+
fn into_static(self) -> AuthRequestData<S::Output> {
217
227
AuthRequestData {
218
228
request_uri: self.request_uri.into_static(),
219
229
authserver_token_endpoint: self.authserver_token_endpoint.into_static(),
···
221
231
.authserver_revocation_endpoint
222
232
.map(|s| s.into_static()),
223
233
pkce_verifier: self.pkce_verifier.into_static(),
224
224
-
dpop_data: self.dpop_data.into_static(),
234
234
+
dpop_data: self.dpop_data,
225
235
state: self.state.into_static(),
226
236
authserver_url: self.authserver_url.into_static(),
227
237
account_did: self.account_did.into_static(),
···
235
245
/// Unlike [`DpopClientData`], this struct only tracks the authserver nonce—no resource-server
236
246
/// nonce is needed until a full session is established.
237
247
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238
238
-
pub struct DpopReqData<'s> {
248
248
+
pub struct DpopReqData {
239
249
/// The private JWK generated fresh for this authorization request and session.
240
250
pub dpop_key: Key,
241
251
/// DPoP nonce received from the authorization server during the PAR exchange, if any.
242
242
-
#[serde(borrow)]
243
243
-
pub dpop_authserver_nonce: Option<CowStr<'s>>,
244
244
-
}
245
245
-
246
246
-
impl IntoStatic for DpopReqData<'_> {
247
247
-
type Output = DpopReqData<'static>;
248
248
-
fn into_static(self) -> DpopReqData<'static> {
249
249
-
DpopReqData {
250
250
-
dpop_key: self.dpop_key,
251
251
-
dpop_authserver_nonce: self.dpop_authserver_nonce.into_static(),
252
252
-
}
253
253
-
}
252
252
+
pub dpop_authserver_nonce: Option<SmolStr>,
254
253
}
255
254
256
256
-
impl DpopDataSource for DpopReqData<'_> {
255
255
+
impl DpopDataSource for DpopReqData {
257
256
fn key(&self) -> &Key {
258
257
&self.dpop_key
259
258
}
260
260
-
fn authserver_nonce(&self) -> Option<CowStr<'_>> {
261
261
-
self.dpop_authserver_nonce.clone()
259
259
+
260
260
+
fn authserver_nonce(&self) -> Option<&str> {
261
261
+
self.dpop_authserver_nonce.as_ref().map(|n| n.as_ref())
262
262
}
263
263
264
264
-
fn host_nonce(&self) -> Option<CowStr<'_>> {
264
264
+
fn host_nonce(&self) -> Option<&str> {
265
265
None
266
266
}
267
267
268
268
-
fn set_authserver_nonce(&mut self, nonce: CowStr<'_>) {
269
269
-
self.dpop_authserver_nonce = Some(nonce.into_static());
268
268
+
fn set_authserver_nonce(&mut self, nonce: SmolStr) {
269
269
+
self.dpop_authserver_nonce = Some(nonce);
270
270
}
271
271
272
272
-
fn set_host_nonce(&mut self, _nonce: CowStr<'_>) {}
272
272
+
fn set_host_nonce(&mut self, _nonce: SmolStr) {}
273
273
}
274
274
275
275
/// Static configuration for an OAuth client: the signing keyset and registered client metadata.
···
277
277
/// `ClientData` is constructed once at startup and shared (via `Arc`) across all sessions
278
278
/// managed by the same [`crate::client::OAuthClient`].
279
279
#[derive(Clone, Debug)]
280
280
-
pub struct ClientData<'s> {
280
280
+
pub struct ClientData<S>
281
281
+
where
282
282
+
S: BosStr + FromStr + Ord,
283
283
+
<S as FromStr>::Err: core::fmt::Debug,
284
284
+
{
281
285
/// Optional private key set used for `private_key_jwt` client authentication.
282
286
/// When `None`, the `none` authentication method is used instead.
283
287
pub keyset: Option<Keyset>,
284
288
/// AT Protocol-specific client registration metadata (redirect URIs, scopes, etc.).
285
285
-
pub config: AtprotoClientMetadata<'s>,
289
289
+
pub config: AtprotoClientMetadata<S>,
286
290
}
287
291
288
288
-
impl<'s> IntoStatic for ClientData<'s> {
289
289
-
type Output = ClientData<'static>;
290
290
-
fn into_static(self) -> ClientData<'static> {
292
292
+
impl<S> IntoStatic for ClientData<S>
293
293
+
where
294
294
+
S: BosStr + FromStr + Ord + IntoStatic,
295
295
+
S::Output: BosStr + Ord + FromStr,
296
296
+
<S as FromStr>::Err: core::fmt::Debug,
297
297
+
<S::Output as FromStr>::Err: core::fmt::Debug,
298
298
+
{
299
299
+
type Output = ClientData<S::Output>;
300
300
+
fn into_static(self) -> ClientData<S::Output> {
291
301
ClientData {
292
302
keyset: self.keyset,
293
303
config: self.config.into_static(),
···
295
305
}
296
306
}
297
307
298
298
-
impl<'s> ClientData<'s> {
308
308
+
impl<S: BosStr + FromStr + Ord> ClientData<S>
309
309
+
where
310
310
+
<S as FromStr>::Err: core::fmt::Debug,
311
311
+
{
299
312
/// Create `ClientData` with an optional signing keyset and the given client metadata.
300
300
-
pub fn new(keyset: Option<Keyset>, config: AtprotoClientMetadata<'s>) -> Self {
313
313
+
pub fn new(keyset: Option<Keyset>, config: AtprotoClientMetadata<S>) -> Self {
301
314
Self { keyset, config }
302
315
}
303
316
···
305
318
///
306
319
/// Suitable for public clients (e.g., single-page applications or native apps) that
307
320
/// cannot securely store a private key.
308
308
-
pub fn new_public(config: AtprotoClientMetadata<'s>) -> Self {
321
321
+
pub fn new_public(config: AtprotoClientMetadata<S>) -> Self {
309
322
Self {
310
323
keyset: None,
311
324
config,
···
318
331
/// `ClientSession` is a convenience type that pairs a [`ClientData`] with a
319
332
/// [`ClientSessionData`] so that methods like `metadata` can access both without requiring
320
333
/// callers to pass them separately.
321
321
-
pub struct ClientSession<'s> {
334
334
+
pub struct ClientSession<S: BosStr = DefaultStr>
335
335
+
where
336
336
+
S: FromStr + Ord,
337
337
+
<S as FromStr>::Err: core::fmt::Debug,
338
338
+
{
322
339
/// Optional signing keyset, forwarded from [`ClientData`].
323
340
pub keyset: Option<Keyset>,
324
341
/// Client registration metadata, forwarded from [`ClientData`].
325
325
-
pub config: AtprotoClientMetadata<'s>,
342
342
+
pub config: AtprotoClientMetadata<S>,
326
343
/// The session state for the authenticated account.
327
327
-
pub session_data: ClientSessionData<'s>,
344
344
+
pub session_data: ClientSessionData<S>,
328
345
}
329
346
330
330
-
impl<'s> ClientSession<'s> {
347
347
+
impl<S: BosStr> ClientSession<S>
348
348
+
where
349
349
+
S: FromStr + Ord + Clone,
350
350
+
<S as FromStr>::Err: core::fmt::Debug,
351
351
+
{
331
352
/// Construct a `ClientSession` from a [`ClientData`] and an active session.
332
353
pub fn new(
333
333
-
ClientData { keyset, config }: ClientData<'s>,
334
334
-
session_data: ClientSessionData<'s>,
354
354
+
ClientData { keyset, config }: ClientData<S>,
355
355
+
session_data: ClientSessionData<S>,
335
356
) -> Self {
336
357
Self {
337
358
keyset,
···
344
365
pub async fn metadata<T: HttpClient + OAuthResolver + Send + Sync>(
345
366
&self,
346
367
client: &T,
347
347
-
) -> Result<OAuthMetadata, Error> {
368
368
+
) -> Result<OAuthMetadata<S>, Error>
369
369
+
where
370
370
+
S: IntoStatic,
371
371
+
{
348
372
Ok(OAuthMetadata {
349
373
server_metadata: client
350
350
-
.get_authorization_server_metadata(&self.session_data.authserver_url)
374
374
+
.get_authorization_server_metadata(self.session_data.authserver_url.as_ref())
351
375
.await
352
376
.map_err(|e| Error::ServerAgent(crate::request::RequestError::resolver(e)))?,
353
353
-
client_metadata: atproto_client_metadata(self.config.clone(), &self.keyset)
354
354
-
.unwrap()
355
355
-
.into_static(),
377
377
+
client_metadata: atproto_client_metadata(&self.config, &self.keyset).unwrap(),
356
378
keyset: self.keyset.clone(),
357
379
})
358
380
}
···
403
425
/// concurrent refresh attempts for the same `(DID, session_id)` pair are coalesced behind
404
426
/// a per-key `Mutex` stored in `pending`, so only one refresh request is issued to the
405
427
/// authorization server even when many concurrent requests detect an expired token.
406
406
-
pub struct SessionRegistry<T, S>
428
428
+
pub struct SessionRegistry<T, S, Str>
407
429
where
408
430
T: OAuthResolver,
409
431
S: ClientAuthStore,
432
432
+
Str: BosStr + FromStr + Ord,
433
433
+
<Str as FromStr>::Err: core::fmt::Debug,
410
434
{
411
435
/// Backing store for persisting session data across process restarts.
412
436
pub store: Arc<S>,
413
437
/// Shared resolver used to fetch authorization server metadata during refresh.
414
438
pub client: Arc<T>,
415
439
/// Static client configuration (keyset and registration metadata).
416
416
-
pub client_data: ClientData<'static>,
440
440
+
pub client_data: ClientData<Str>,
417
441
/// Per-`(DID, session_id)` mutex that serializes concurrent refresh attempts.
418
442
pending: DashMap<SmolStr, Arc<Mutex<()>>>,
419
443
}
420
444
421
421
-
impl<T, S> SessionRegistry<T, S>
445
445
+
impl<T, S, Str> SessionRegistry<T, S, Str>
422
446
where
423
447
S: ClientAuthStore,
424
448
T: OAuthResolver,
449
449
+
Str: BosStr + FromStr + Ord,
450
450
+
<Str as FromStr>::Err: core::fmt::Debug,
425
451
{
426
452
/// Create a new registry, taking ownership of the store.
427
427
-
pub fn new(store: S, client: Arc<T>, client_data: ClientData<'static>) -> Self {
453
453
+
pub fn new(store: S, client: Arc<T>, client_data: ClientData<Str>) -> Self {
428
454
let store = Arc::new(store);
429
455
Self {
430
456
store: Arc::clone(&store),
···
438
464
///
439
465
/// Use this variant when the store needs to be accessed from outside the registry,
440
466
/// for example to expose session listing or administration functionality.
441
441
-
pub fn new_shared(store: Arc<S>, client: Arc<T>, client_data: ClientData<'static>) -> Self {
467
467
+
pub fn new_shared(store: Arc<S>, client: Arc<T>, client_data: ClientData<Str>) -> Self {
442
468
Self {
443
469
store,
444
470
client,
···
448
474
}
449
475
}
450
476
451
451
-
impl<T, S> SessionRegistry<T, S>
477
477
+
impl<T, S, Str> SessionRegistry<T, S, Str>
452
478
where
453
479
S: ClientAuthStore + Send + Sync + 'static,
454
480
T: OAuthResolver + DpopExt + Send + Sync + 'static,
481
481
+
Str: BosStr + FromStr + Ord + Clone,
482
482
+
<Str as FromStr>::Err: core::fmt::Debug,
455
483
{
456
456
-
async fn get_refreshed(
484
484
+
async fn get_refreshed<D: BosStr + Send + Sync>(
457
485
&self,
458
458
-
did: &Did<'_>,
486
486
+
did: &Did<D>,
459
487
session_id: &str,
460
460
-
) -> Result<ClientSessionData<'_>, Error> {
488
488
+
) -> Result<ClientSessionData, Error> {
461
489
let key = format_smolstr!("{}_{}", did, session_id);
462
490
let lock = self
463
491
.pending
···
506
534
/// When `refresh` is `true`, proactively
507
535
/// renews the token if it is within 60 seconds of expiry. When `false`, returns the session
508
536
/// data as-is without contacting the authorization server.
509
509
-
pub async fn get(
537
537
+
pub async fn get<D: BosStr + Send + Sync>(
510
538
&self,
511
511
-
did: &Did<'_>,
539
539
+
did: &Did<D>,
512
540
session_id: &str,
513
541
refresh: bool,
514
514
-
) -> Result<ClientSessionData<'_>, Error> {
542
542
+
) -> Result<ClientSessionData, Error> {
515
543
if refresh {
516
544
self.get_refreshed(did, session_id).await
517
545
} else {
···
523
551
}
524
552
}
525
553
/// Persist an updated session to the backing store.
526
526
-
pub async fn set(&self, value: ClientSessionData<'_>) -> Result<(), Error> {
554
554
+
pub async fn set(&self, value: ClientSessionData) -> Result<(), Error> {
527
555
self.store.upsert_session(value).await?;
528
556
Ok(())
529
557
}
530
558
/// Delete a session from the backing store.
531
531
-
pub async fn del(&self, did: &Did<'_>, session_id: &str) -> Result<(), Error> {
559
559
+
pub async fn del<D: BosStr + Send + Sync>(
560
560
+
&self,
561
561
+
did: &Did<D>,
562
562
+
session_id: &str,
563
563
+
) -> Result<(), Error> {
532
564
self.store.delete_session(did, session_id).await?;
533
565
Ok(())
534
566
}
···
13
13
pub use self::token::*;
14
14
use jacquard_common::CowStr;
15
15
use jacquard_common::IntoStatic;
16
16
+
use jacquard_common::bos::{BosStr, DefaultStr};
16
17
use jacquard_common::deps::fluent_uri::Uri;
17
18
use serde::Deserialize;
19
19
+
use smol_str::SmolStr;
18
20
19
21
/// The `prompt` parameter for an OAuth authorization request.
20
22
///
···
34
36
35
37
impl From<AuthorizeOptionPrompt> for CowStr<'static> {
36
38
fn from(value: AuthorizeOptionPrompt) -> Self {
39
39
+
CowStr::new_static(value.into())
40
40
+
}
41
41
+
}
42
42
+
43
43
+
impl From<AuthorizeOptionPrompt> for SmolStr {
44
44
+
fn from(value: AuthorizeOptionPrompt) -> Self {
45
45
+
SmolStr::new_static(value.into())
46
46
+
}
47
47
+
}
48
48
+
49
49
+
impl From<AuthorizeOptionPrompt> for &'static str {
50
50
+
fn from(value: AuthorizeOptionPrompt) -> Self {
37
51
match value {
38
38
-
AuthorizeOptionPrompt::Login => CowStr::new_static("login"),
39
39
-
AuthorizeOptionPrompt::None => CowStr::new_static("none"),
40
40
-
AuthorizeOptionPrompt::Consent => CowStr::new_static("consent"),
41
41
-
AuthorizeOptionPrompt::SelectAccount => CowStr::new_static("select_account"),
52
52
+
AuthorizeOptionPrompt::Login => "login",
53
53
+
AuthorizeOptionPrompt::None => "none",
54
54
+
AuthorizeOptionPrompt::Consent => "consent",
55
55
+
AuthorizeOptionPrompt::SelectAccount => "select_account",
42
56
}
43
57
}
44
58
}
45
59
46
60
/// Options for initiating an OAuth authorization request.
47
61
#[derive(Debug)]
48
48
-
pub struct AuthorizeOptions<'s> {
62
62
+
pub struct AuthorizeOptions<S: BosStr = DefaultStr> {
49
63
/// Override the redirect URI registered in the client metadata.
50
64
pub redirect_uri: Option<Uri<String>>,
51
65
/// Scopes to request. Defaults to an empty list (server-defined defaults apply).
52
52
-
pub scopes: Vec<Scope<'s>>,
66
66
+
pub scopes: Vec<Scope<S>>,
53
67
/// Optional prompt hint for the authorization server's UI.
54
68
pub prompt: Option<AuthorizeOptionPrompt>,
55
69
/// Opaque client-provided state value, echoed back in the callback for CSRF protection.
56
56
-
pub state: Option<CowStr<'s>>,
70
70
+
pub state: Option<S>,
57
71
}
58
72
59
59
-
impl Default for AuthorizeOptions<'_> {
73
73
+
impl<S: BosStr> Default for AuthorizeOptions<S> {
60
74
fn default() -> Self {
61
75
Self {
62
76
redirect_uri: None,
···
67
81
}
68
82
}
69
83
70
70
-
impl<'s> AuthorizeOptions<'s> {
84
84
+
impl<S: BosStr> AuthorizeOptions<S> {
71
85
/// Set the `prompt` parameter sent to the authorization server.
72
86
pub fn with_prompt(mut self, prompt: AuthorizeOptionPrompt) -> Self {
73
87
self.prompt = Some(prompt);
···
75
89
}
76
90
77
91
/// Set a CSRF-protection `state` value to be echoed in the callback.
78
78
-
pub fn with_state(mut self, state: CowStr<'s>) -> Self {
92
92
+
pub fn with_state(mut self, state: S) -> Self {
79
93
self.state = Some(state);
80
94
self
81
95
}
···
87
101
}
88
102
89
103
/// Set the OAuth scopes to request.
90
90
-
pub fn with_scopes(mut self, scopes: Vec<Scope<'s>>) -> Self {
104
104
+
pub fn with_scopes(mut self, scopes: Vec<Scope<S>>) -> Self {
91
105
self.scopes = scopes;
92
106
self
93
107
}
···
95
109
96
110
/// Query parameters delivered to the OAuth redirect URI after user authorization.
97
111
#[derive(Debug, Deserialize)]
98
98
-
pub struct CallbackParams<'s> {
112
112
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
113
113
+
pub struct CallbackParams<S: BosStr = DefaultStr> {
99
114
/// The authorization code issued by the authorization server.
100
100
-
#[serde(borrow)]
101
101
-
pub code: CowStr<'s>,
115
115
+
pub code: S,
102
116
/// The `state` value originally sent in the authorization request, used to
103
117
/// verify the response belongs to this session.
104
104
-
pub state: Option<CowStr<'s>>,
118
118
+
pub state: Option<S>,
105
119
/// The `iss` (issuer) parameter, required by RFC 9207 to prevent mix-up attacks.
106
106
-
pub iss: Option<CowStr<'s>>,
120
120
+
pub iss: Option<S>,
107
121
}
108
122
109
109
-
impl IntoStatic for CallbackParams<'_> {
110
110
-
type Output = CallbackParams<'static>;
123
123
+
impl<S: BosStr + IntoStatic> IntoStatic for CallbackParams<S>
124
124
+
where
125
125
+
S::Output: BosStr,
126
126
+
{
127
127
+
type Output = CallbackParams<S::Output>;
111
128
112
129
fn into_static(self) -> Self::Output {
113
130
CallbackParams {
114
131
code: self.code.into_static(),
115
115
-
state: self.state.map(|s| s.into_static()),
116
116
-
iss: self.iss.map(|s| s.into_static()),
132
132
+
state: self.state.into_static(),
133
133
+
iss: self.iss.into_static(),
117
134
}
118
135
}
119
136
}
···
1
1
-
use jacquard_common::{CowStr, IntoStatic};
1
1
+
use jacquard_common::IntoStatic;
2
2
+
use jacquard_common::bos::{BosStr, DefaultStr};
2
3
use jose_jwk::JwkSet;
3
4
use serde::{Deserialize, Serialize};
4
4
-
use smol_str::SmolStr;
5
5
6
6
/// OAuth 2.1 client metadata, used in the ATProto client ID metadata document.
7
7
///
···
11
11
///
12
12
/// <https://atproto.com/specs/oauth>
13
13
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
14
14
-
pub struct OAuthClientMetadata<'c> {
14
14
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
15
15
+
pub struct OAuthClientMetadata<S: BosStr = DefaultStr> {
15
16
/// The client identifier, typically a URL pointing to this metadata document.
16
16
-
pub client_id: CowStr<'c>,
17
17
+
pub client_id: S,
17
18
/// URL of the client's home page, used for display purposes.
18
19
#[serde(skip_serializing_if = "Option::is_none")]
19
19
-
pub client_uri: Option<CowStr<'c>>,
20
20
+
pub client_uri: Option<S>,
20
21
/// List of redirect URIs the authorization server may send callbacks to.
21
21
-
pub redirect_uris: Vec<CowStr<'c>>,
22
22
+
pub redirect_uris: Vec<S>,
22
23
/// Space-separated list of scopes the client is allowed to request.
23
24
#[serde(skip_serializing_if = "Option::is_none")]
24
24
-
#[serde(borrow)]
25
25
-
pub scope: Option<CowStr<'c>>,
25
25
+
pub scope: Option<S>,
26
26
/// Application type (`web` or `native`), used to enforce redirect URI constraints.
27
27
#[serde(skip_serializing_if = "Option::is_none")]
28
28
-
pub application_type: Option<CowStr<'c>>,
28
28
+
pub application_type: Option<S>,
29
29
/// OAuth 2.0 grant types the client will use.
30
30
#[serde(skip_serializing_if = "Option::is_none")]
31
31
-
pub grant_types: Option<Vec<CowStr<'c>>>,
31
31
+
pub grant_types: Option<Vec<S>>,
32
32
/// Authentication method the client uses at the token endpoint.
33
33
#[serde(skip_serializing_if = "Option::is_none")]
34
34
-
pub token_endpoint_auth_method: Option<CowStr<'c>>,
34
34
+
pub token_endpoint_auth_method: Option<S>,
35
35
/// Response types the client will use in authorization requests.
36
36
-
pub response_types: Vec<CowStr<'c>>,
36
36
+
pub response_types: Vec<S>,
37
37
/// If `true`, the client requires DPoP-bound access tokens (RFC 9449 §5.2).
38
38
///
39
39
/// <https://datatracker.ietf.org/doc/html/rfc9449#section-5.2>
···
43
43
///
44
44
/// <https://datatracker.ietf.org/doc/html/rfc7591#section-2>
45
45
#[serde(skip_serializing_if = "Option::is_none")]
46
46
-
pub jwks_uri: Option<CowStr<'c>>,
46
46
+
pub jwks_uri: Option<S>,
47
47
/// Inline JWK Set for verifying signed requests, alternative to `jwks_uri`.
48
48
#[serde(skip_serializing_if = "Option::is_none")]
49
49
pub jwks: Option<JwkSet>,
···
51
51
///
52
52
/// <https://openid.net/specs/openid-connect-registration-1_0.html#ClientMetadata>
53
53
#[serde(skip_serializing_if = "Option::is_none")]
54
54
-
pub token_endpoint_auth_signing_alg: Option<CowStr<'c>>,
54
54
+
pub token_endpoint_auth_signing_alg: Option<S>,
55
55
/// Human-readable name of the client, shown to users during authorization.
56
56
#[serde(skip_serializing_if = "Option::is_none")]
57
57
-
pub client_name: Option<SmolStr>,
57
57
+
pub client_name: Option<S>,
58
58
/// URL of the client's logo image.
59
59
#[serde(skip_serializing_if = "Option::is_none")]
60
60
-
pub logo_uri: Option<CowStr<'c>>,
60
60
+
pub logo_uri: Option<S>,
61
61
/// URL of the client's terms of service.
62
62
#[serde(skip_serializing_if = "Option::is_none")]
63
63
-
pub tos_uri: Option<CowStr<'c>>,
63
63
+
pub tos_uri: Option<S>,
64
64
/// URL of the client's privacy policy.
65
65
#[serde(skip_serializing_if = "Option::is_none")]
66
66
-
pub privacy_policy_uri: Option<CowStr<'c>>,
66
66
+
pub privacy_policy_uri: Option<S>,
67
67
}
68
68
69
69
-
impl OAuthClientMetadata<'_> {}
69
69
+
impl<S: BosStr> OAuthClientMetadata<S> {}
70
70
71
71
-
impl IntoStatic for OAuthClientMetadata<'_> {
72
72
-
type Output = OAuthClientMetadata<'static>;
71
71
+
impl<S: BosStr + IntoStatic> IntoStatic for OAuthClientMetadata<S>
72
72
+
where
73
73
+
S::Output: BosStr,
74
74
+
{
75
75
+
type Output = OAuthClientMetadata<S::Output>;
73
76
74
77
fn into_static(self) -> Self::Output {
75
78
OAuthClientMetadata {
76
79
client_id: self.client_id.into_static(),
77
80
client_uri: self.client_uri.into_static(),
78
81
redirect_uris: self.redirect_uris.into_static(),
79
79
-
scope: self.scope.map(|scope| scope.into_static()),
80
80
-
application_type: self.application_type.map(|app_type| app_type.into_static()),
81
81
-
grant_types: self.grant_types.map(|types| types.into_static()),
82
82
+
scope: self.scope.into_static(),
83
83
+
application_type: self.application_type.into_static(),
84
84
+
grant_types: self.grant_types.into_static(),
82
85
response_types: self.response_types.into_static(),
83
83
-
token_endpoint_auth_method: self
84
84
-
.token_endpoint_auth_method
85
85
-
.map(|method| method.into_static()),
86
86
+
token_endpoint_auth_method: self.token_endpoint_auth_method.into_static(),
86
87
dpop_bound_access_tokens: self.dpop_bound_access_tokens,
87
88
jwks_uri: self.jwks_uri.into_static(),
88
89
jwks: self.jwks,
89
89
-
token_endpoint_auth_signing_alg: self
90
90
-
.token_endpoint_auth_signing_alg
91
91
-
.map(|alg| alg.into_static()),
92
92
-
client_name: self.client_name,
90
90
+
token_endpoint_auth_signing_alg: self.token_endpoint_auth_signing_alg.into_static(),
91
91
+
client_name: self.client_name.into_static(),
93
92
logo_uri: self.logo_uri.into_static(),
94
93
tos_uri: self.tos_uri.into_static(),
95
94
privacy_policy_uri: self.privacy_policy_uri.into_static(),
···
1
1
-
use jacquard_common::{CowStr, IntoStatic, types::string::Language};
1
1
+
use jacquard_common::bos::{BosStr, DefaultStr};
2
2
+
use jacquard_common::{IntoStatic, types::string::Language};
2
3
use serde::{Deserialize, Serialize};
3
4
4
5
/// Authorization server metadata, as returned from the
···
7
8
/// Defined by [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414#section-2)
8
9
/// with extensions from OpenID Connect Discovery, RFC 9126 (PAR), RFC 9207,
9
10
/// RFC 9449 (DPoP), and the ATProto client ID metadata document draft.
10
10
-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
11
11
-
pub struct OAuthAuthorizationServerMetadata<'s> {
11
11
+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
12
12
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
13
13
+
pub struct OAuthAuthorizationServerMetadata<S: BosStr = DefaultStr> {
12
14
/// The issuer identifier URL of the authorization server.
13
15
///
14
16
/// <https://datatracker.ietf.org/doc/html/rfc8414#section-2>
15
15
-
#[serde(borrow)]
16
16
-
pub issuer: CowStr<'s>,
17
17
+
pub issuer: S,
17
18
/// The URL of the authorization endpoint.
18
18
-
pub authorization_endpoint: CowStr<'s>, // optional?
19
19
+
pub authorization_endpoint: S, // optional?
19
20
/// The URL of the token endpoint.
20
20
-
pub token_endpoint: CowStr<'s>, // optional?
21
21
+
pub token_endpoint: S, // optional?
21
22
/// URL of the authorization server's JWK Set document.
22
22
-
pub jwks_uri: Option<CowStr<'s>>,
23
23
+
pub jwks_uri: Option<S>,
23
24
/// URL of the dynamic client registration endpoint, if supported.
24
24
-
pub registration_endpoint: Option<CowStr<'s>>,
25
25
+
pub registration_endpoint: Option<S>,
25
26
/// List of OAuth 2.0 scope values the server supports.
26
26
-
pub scopes_supported: Vec<CowStr<'s>>,
27
27
+
pub scopes_supported: Vec<S>,
27
28
/// List of OAuth 2.0 response type values the server supports.
28
28
-
pub response_types_supported: Vec<CowStr<'s>>,
29
29
+
pub response_types_supported: Vec<S>,
29
30
/// List of OAuth 2.0 response mode values the server supports.
30
30
-
pub response_modes_supported: Option<Vec<CowStr<'s>>>,
31
31
+
pub response_modes_supported: Option<Vec<S>>,
31
32
/// List of OAuth 2.0 grant type values the server supports.
32
32
-
pub grant_types_supported: Option<Vec<CowStr<'s>>>,
33
33
+
pub grant_types_supported: Option<Vec<S>>,
33
34
/// List of client authentication methods supported at the token endpoint.
34
34
-
pub token_endpoint_auth_methods_supported: Option<Vec<CowStr<'s>>>,
35
35
+
pub token_endpoint_auth_methods_supported: Option<Vec<S>>,
35
36
/// List of JWS signing algorithms supported for token endpoint auth.
36
36
-
pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<CowStr<'s>>>,
37
37
+
pub token_endpoint_auth_signing_alg_values_supported: Option<Vec<S>>,
37
38
/// URL of a page with human-readable information about the server.
38
38
-
pub service_documentation: Option<CowStr<'s>>,
39
39
+
pub service_documentation: Option<S>,
39
40
/// BCP 47 language tags for UI locales the server supports.
40
41
pub ui_locales_supported: Option<Vec<Language>>,
41
42
/// URL of the authorization server's privacy policy.
42
42
-
pub op_policy_uri: Option<CowStr<'s>>,
43
43
+
pub op_policy_uri: Option<S>,
43
44
/// URL of the authorization server's terms of service.
44
44
-
pub op_tos_uri: Option<CowStr<'s>>,
45
45
+
pub op_tos_uri: Option<S>,
45
46
/// URL of the token revocation endpoint (RFC 7009).
46
46
-
pub revocation_endpoint: Option<CowStr<'s>>,
47
47
+
pub revocation_endpoint: Option<S>,
47
48
/// List of client authentication methods supported at the revocation endpoint.
48
48
-
pub revocation_endpoint_auth_methods_supported: Option<Vec<CowStr<'s>>>,
49
49
+
pub revocation_endpoint_auth_methods_supported: Option<Vec<S>>,
49
50
/// List of JWS signing algorithms supported for revocation endpoint auth.
50
50
-
pub revocation_endpoint_auth_signing_alg_values_supported: Option<Vec<CowStr<'s>>>,
51
51
+
pub revocation_endpoint_auth_signing_alg_values_supported: Option<Vec<S>>,
51
52
/// URL of the token introspection endpoint (RFC 7662).
52
52
-
pub introspection_endpoint: Option<CowStr<'s>>,
53
53
+
pub introspection_endpoint: Option<S>,
53
54
/// List of client authentication methods supported at the introspection endpoint.
54
54
-
pub introspection_endpoint_auth_methods_supported: Option<Vec<CowStr<'s>>>,
55
55
+
pub introspection_endpoint_auth_methods_supported: Option<Vec<S>>,
55
56
/// List of JWS signing algorithms supported for introspection endpoint auth.
56
56
-
pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<CowStr<'s>>>,
57
57
+
pub introspection_endpoint_auth_signing_alg_values_supported: Option<Vec<S>>,
57
58
/// PKCE code challenge methods supported by the server.
58
58
-
pub code_challenge_methods_supported: Option<Vec<CowStr<'s>>>,
59
59
+
pub code_challenge_methods_supported: Option<Vec<S>>,
59
60
60
61
/// Subject identifier types supported (`public` or `pairwise`).
61
62
///
62
63
/// <https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata>
63
63
-
pub subject_types_supported: Option<Vec<CowStr<'s>>>,
64
64
+
pub subject_types_supported: Option<Vec<S>>,
64
65
/// If `true`, clients must pre-register `request_uri` values.
65
66
pub require_request_uri_registration: Option<bool>,
66
67
67
68
/// URL of the Pushed Authorization Request (PAR) endpoint (RFC 9126).
68
69
///
69
70
/// <https://datatracker.ietf.org/doc/html/rfc9126#section-5>
70
70
-
pub pushed_authorization_request_endpoint: Option<CowStr<'s>>,
71
71
+
pub pushed_authorization_request_endpoint: Option<S>,
71
72
/// If `true`, all authorization requests must use PAR.
72
73
pub require_pushed_authorization_requests: Option<bool>,
73
74
···
79
80
/// DPoP JWS signing algorithms supported by this server (RFC 9449).
80
81
///
81
82
/// <https://datatracker.ietf.org/doc/html/rfc9449#section-5.1>
82
82
-
pub dpop_signing_alg_values_supported: Option<Vec<CowStr<'s>>>,
83
83
+
pub dpop_signing_alg_values_supported: Option<Vec<S>>,
83
84
84
85
/// If `true`, the server supports the ATProto client ID metadata document extension.
85
86
///
···
89
90
/// Protected resources associated with this authorization server.
90
91
///
91
92
/// <https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-08#name-authorization-server-metada>
92
92
-
pub protected_resources: Option<Vec<CowStr<'s>>>,
93
93
+
pub protected_resources: Option<Vec<S>>,
94
94
+
}
95
95
+
96
96
+
impl<S: BosStr> Default for OAuthAuthorizationServerMetadata<S> {
97
97
+
fn default() -> Self {
98
98
+
OAuthAuthorizationServerMetadata {
99
99
+
issuer: S::from_static(""),
100
100
+
authorization_endpoint: S::from_static(""),
101
101
+
token_endpoint: S::from_static(""),
102
102
+
jwks_uri: None,
103
103
+
registration_endpoint: None,
104
104
+
scopes_supported: Vec::new(),
105
105
+
response_types_supported: Vec::new(),
106
106
+
response_modes_supported: None,
107
107
+
grant_types_supported: None,
108
108
+
token_endpoint_auth_methods_supported: None,
109
109
+
token_endpoint_auth_signing_alg_values_supported: None,
110
110
+
service_documentation: None,
111
111
+
ui_locales_supported: None,
112
112
+
op_policy_uri: None,
113
113
+
op_tos_uri: None,
114
114
+
revocation_endpoint: None,
115
115
+
revocation_endpoint_auth_methods_supported: None,
116
116
+
revocation_endpoint_auth_signing_alg_values_supported: None,
117
117
+
introspection_endpoint: None,
118
118
+
introspection_endpoint_auth_methods_supported: None,
119
119
+
introspection_endpoint_auth_signing_alg_values_supported: None,
120
120
+
code_challenge_methods_supported: None,
121
121
+
subject_types_supported: None,
122
122
+
require_request_uri_registration: None,
123
123
+
pushed_authorization_request_endpoint: None,
124
124
+
require_pushed_authorization_requests: None,
125
125
+
authorization_response_iss_parameter_supported: None,
126
126
+
dpop_signing_alg_values_supported: None,
127
127
+
client_id_metadata_document_supported: None,
128
128
+
protected_resources: None,
129
129
+
}
130
130
+
}
93
131
}
94
132
95
133
/// Protected resource metadata, returned from `.well-known/oauth-protected-resource`.
···
97
135
/// Allows clients to discover which authorization servers protect a given resource
98
136
/// and what scopes and bearer methods are accepted. Defined by
99
137
/// [draft-ietf-oauth-resource-metadata](https://datatracker.ietf.org/doc/draft-ietf-oauth-resource-metadata/).
100
100
-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
101
101
-
pub struct OAuthProtectedResourceMetadata<'s> {
138
138
+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
139
139
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
140
140
+
pub struct OAuthProtectedResourceMetadata<S: BosStr = DefaultStr> {
102
141
/// The URL of the protected resource itself.
103
103
-
#[serde(borrow)]
104
104
-
pub resource: CowStr<'s>,
142
142
+
pub resource: S,
105
143
/// URLs of authorization servers that can issue tokens for this resource.
106
106
-
pub authorization_servers: Option<Vec<CowStr<'s>>>,
144
144
+
pub authorization_servers: Option<Vec<S>>,
107
145
/// URL of the resource server's JWK Set document.
108
108
-
pub jwks_uri: Option<CowStr<'s>>,
146
146
+
pub jwks_uri: Option<S>,
109
147
/// List of OAuth 2.0 scope values the resource server supports.
110
110
-
pub scopes_supported: Vec<CowStr<'s>>,
148
148
+
pub scopes_supported: Vec<S>,
111
149
/// Bearer token presentation methods supported (`header`, `body`, `query`).
112
112
-
pub bearer_methods_supported: Option<Vec<CowStr<'s>>>,
150
150
+
pub bearer_methods_supported: Option<Vec<S>>,
113
151
/// JWS signing algorithms supported for resource-bound tokens.
114
114
-
pub resource_signing_alg_values_supported: Option<Vec<CowStr<'s>>>,
152
152
+
pub resource_signing_alg_values_supported: Option<Vec<S>>,
115
153
/// URL of a page with human-readable information about the resource.
116
116
-
pub resource_documentation: Option<CowStr<'s>>,
154
154
+
pub resource_documentation: Option<S>,
117
155
/// URL of the resource server's privacy policy.
118
118
-
pub resource_policy_uri: Option<CowStr<'s>>,
156
156
+
pub resource_policy_uri: Option<S>,
119
157
/// URL of the resource server's terms of service.
120
120
-
pub resource_tos_uri: Option<CowStr<'s>>,
158
158
+
pub resource_tos_uri: Option<S>,
159
159
+
}
160
160
+
161
161
+
impl<S: BosStr> Default for OAuthProtectedResourceMetadata<S> {
162
162
+
fn default() -> Self {
163
163
+
OAuthProtectedResourceMetadata {
164
164
+
resource: S::from_static(""),
165
165
+
authorization_servers: None,
166
166
+
jwks_uri: None,
167
167
+
scopes_supported: Vec::new(),
168
168
+
bearer_methods_supported: None,
169
169
+
resource_signing_alg_values_supported: None,
170
170
+
resource_documentation: None,
171
171
+
resource_policy_uri: None,
172
172
+
resource_tos_uri: None,
173
173
+
}
174
174
+
}
121
175
}
122
176
123
123
-
impl IntoStatic for OAuthProtectedResourceMetadata<'_> {
124
124
-
type Output = OAuthProtectedResourceMetadata<'static>;
177
177
+
impl<S: BosStr + IntoStatic> IntoStatic for OAuthProtectedResourceMetadata<S>
178
178
+
where
179
179
+
S::Output: BosStr,
180
180
+
{
181
181
+
type Output = OAuthProtectedResourceMetadata<S::Output>;
125
182
fn into_static(self) -> Self::Output {
126
183
OAuthProtectedResourceMetadata {
127
184
resource: self.resource.into_static(),
128
185
authorization_servers: self.authorization_servers.into_static(),
129
129
-
jwks_uri: self.jwks_uri.map(|v| v.into_static()),
186
186
+
jwks_uri: self.jwks_uri.into_static(),
130
187
scopes_supported: self.scopes_supported.into_static(),
131
131
-
bearer_methods_supported: self.bearer_methods_supported.map(|v| v.into_static()),
188
188
+
bearer_methods_supported: self.bearer_methods_supported.into_static(),
132
189
resource_signing_alg_values_supported: self
133
190
.resource_signing_alg_values_supported
134
134
-
.map(|v| v.into_static()),
135
135
-
resource_documentation: self.resource_documentation.map(|v| v.into_static()),
136
136
-
resource_policy_uri: self.resource_policy_uri.map(|v| v.into_static()),
137
137
-
resource_tos_uri: self.resource_tos_uri.map(|v| v.into_static()),
191
191
+
.into_static(),
192
192
+
resource_documentation: self.resource_documentation.into_static(),
193
193
+
resource_policy_uri: self.resource_policy_uri.into_static(),
194
194
+
resource_tos_uri: self.resource_tos_uri.into_static(),
138
195
}
139
196
}
140
197
}
141
198
142
142
-
impl IntoStatic for OAuthAuthorizationServerMetadata<'_> {
143
143
-
type Output = OAuthAuthorizationServerMetadata<'static>;
199
199
+
impl<S: BosStr + IntoStatic> IntoStatic for OAuthAuthorizationServerMetadata<S>
200
200
+
where
201
201
+
S::Output: BosStr,
202
202
+
{
203
203
+
type Output = OAuthAuthorizationServerMetadata<S::Output>;
144
204
fn into_static(self) -> Self::Output {
145
205
OAuthAuthorizationServerMetadata {
146
206
issuer: self.issuer.into_static(),
···
178
238
.into_static(),
179
239
code_challenge_methods_supported: self.code_challenge_methods_supported.into_static(),
180
240
subject_types_supported: self.subject_types_supported.into_static(),
181
181
-
require_request_uri_registration: self.require_request_uri_registration.into_static(),
241
241
+
require_request_uri_registration: self.require_request_uri_registration,
182
242
pushed_authorization_request_endpoint: self
183
243
.pushed_authorization_request_endpoint
184
244
.into_static(),
185
185
-
require_pushed_authorization_requests: self
186
186
-
.require_pushed_authorization_requests
187
187
-
.into_static(),
245
245
+
require_pushed_authorization_requests: self.require_pushed_authorization_requests,
188
246
authorization_response_iss_parameter_supported: self
189
189
-
.authorization_response_iss_parameter_supported
190
190
-
.into_static(),
247
247
+
.authorization_response_iss_parameter_supported,
191
248
dpop_signing_alg_values_supported: self.dpop_signing_alg_values_supported.into_static(),
192
192
-
client_id_metadata_document_supported: self
193
193
-
.client_id_metadata_document_supported
194
194
-
.into_static(),
249
249
+
client_id_metadata_document_supported: self.client_id_metadata_document_supported,
195
250
protected_resources: self.protected_resources.into_static(),
196
251
}
197
252
}
···
1
1
-
use jacquard_common::{CowStr, IntoStatic};
1
1
+
use jacquard_common::IntoStatic;
2
2
+
use jacquard_common::bos::{BosStr, DefaultStr};
2
3
use serde::{Deserialize, Serialize};
3
4
4
5
/// The `response_type` parameter for an OAuth 2.0 authorization request.
···
51
52
/// authorization server before redirecting the user, improving security by keeping
52
53
/// parameters out of the browser URL.
53
54
#[derive(Serialize, Deserialize, Debug)]
54
54
-
pub struct ParParameters<'a> {
55
55
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
56
56
+
pub struct ParParameters<S: BosStr = DefaultStr> {
55
57
/// The response type to request (e.g. `code`).
56
58
///
57
59
/// <https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1>
58
60
pub response_type: AuthorizationResponseType,
59
61
/// The redirect URI where the authorization response will be sent.
60
60
-
#[serde(borrow)]
61
61
-
pub redirect_uri: CowStr<'a>,
62
62
+
pub redirect_uri: S,
62
63
/// An opaque CSRF state value to be echoed back in the callback.
63
63
-
pub state: CowStr<'a>,
64
64
+
pub state: S,
64
65
/// Space-separated list of requested scopes.
65
65
-
pub scope: Option<CowStr<'a>>,
66
66
+
pub scope: Option<S>,
66
67
/// How the authorization response parameters are delivered to the client.
67
68
///
68
69
/// <https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes>
···
70
71
/// The PKCE code challenge derived from the code verifier.
71
72
///
72
73
/// <https://datatracker.ietf.org/doc/html/rfc7636#section-4.3>
73
73
-
pub code_challenge: CowStr<'a>,
74
74
+
pub code_challenge: S,
74
75
/// The method used to derive the code challenge.
75
76
pub code_challenge_method: AuthorizationCodeChallengeMethod,
76
77
/// Hint to pre-fill the login form with a handle or email.
77
78
///
78
79
/// <https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest>
79
79
-
pub login_hint: Option<CowStr<'a>>,
80
80
+
pub login_hint: Option<S>,
80
81
/// Prompt hint controlling authorization server UI behavior.
81
81
-
pub prompt: Option<CowStr<'a>>,
82
82
+
pub prompt: Option<S>,
82
83
}
83
84
84
85
/// The `grant_type` parameter for a token endpoint request.
···
93
94
94
95
/// Parameters for exchanging an authorization code for tokens (RFC 6749 §4.1.3).
95
96
#[derive(Serialize, Deserialize)]
96
96
-
pub struct TokenRequestParameters<'a> {
97
97
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
98
98
+
pub struct TokenRequestParameters<S: BosStr = DefaultStr> {
97
99
/// Must be `authorization_code` for the authorization code grant.
98
100
///
99
101
/// <https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3>
100
102
pub grant_type: TokenGrantType,
101
103
/// The authorization code received from the authorization server.
102
102
-
#[serde(borrow)]
103
103
-
pub code: CowStr<'a>,
104
104
+
pub code: S,
104
105
/// The redirect URI used in the original authorization request.
105
105
-
pub redirect_uri: CowStr<'a>,
106
106
+
pub redirect_uri: S,
106
107
/// The PKCE code verifier that was used to generate the code challenge (RFC 7636 §4.5).
107
108
///
108
109
/// <https://datatracker.ietf.org/doc/html/rfc7636#section-4.5>
109
109
-
pub code_verifier: CowStr<'a>,
110
110
+
pub code_verifier: S,
110
111
}
111
112
112
113
/// Parameters for refreshing an access token using a refresh token (RFC 6749 §6).
113
114
#[derive(Serialize, Deserialize)]
114
114
-
pub struct RefreshRequestParameters<'a> {
115
115
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
116
116
+
pub struct RefreshRequestParameters<S: BosStr = DefaultStr> {
115
117
/// Must be `refresh_token` for the refresh grant.
116
118
///
117
119
/// <https://datatracker.ietf.org/doc/html/rfc6749#section-6>
118
120
pub grant_type: TokenGrantType,
119
121
/// The refresh token previously issued to the client.
120
120
-
#[serde(borrow)]
121
121
-
pub refresh_token: CowStr<'a>,
122
122
+
pub refresh_token: S,
122
123
/// Optional scope to request; must not exceed the originally granted scope.
123
123
-
pub scope: Option<CowStr<'a>>,
124
124
+
pub scope: Option<S>,
124
125
}
125
126
126
127
/// Parameters for a token revocation request (RFC 7009 §2.1).
···
130
131
///
131
132
/// <https://datatracker.ietf.org/doc/html/rfc7009#section-2.1>
132
133
#[derive(Serialize, Deserialize)]
133
133
-
pub struct RevocationRequestParameters<'a> {
134
134
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
135
135
+
pub struct RevocationRequestParameters<S: BosStr = DefaultStr> {
134
136
/// The token to be revoked.
135
135
-
#[serde(borrow)]
136
136
-
pub token: CowStr<'a>,
137
137
+
pub token: S,
137
138
// ?
138
139
// pub token_type_hint: Option<String>,
139
140
}
140
141
141
141
-
impl IntoStatic for RevocationRequestParameters<'_> {
142
142
-
type Output = RevocationRequestParameters<'static>;
142
142
+
impl<S: BosStr + IntoStatic> IntoStatic for RevocationRequestParameters<S>
143
143
+
where
144
144
+
S::Output: BosStr,
145
145
+
{
146
146
+
type Output = RevocationRequestParameters<S::Output>;
143
147
144
148
fn into_static(self) -> Self::Output {
145
149
Self::Output {
···
148
152
}
149
153
}
150
154
151
151
-
impl IntoStatic for TokenRequestParameters<'_> {
152
152
-
type Output = TokenRequestParameters<'static>;
155
155
+
impl<S: BosStr + IntoStatic> IntoStatic for TokenRequestParameters<S>
156
156
+
where
157
157
+
S::Output: BosStr,
158
158
+
{
159
159
+
type Output = TokenRequestParameters<S::Output>;
153
160
154
161
fn into_static(self) -> Self::Output {
155
162
Self::Output {
···
161
168
}
162
169
}
163
170
164
164
-
impl IntoStatic for RefreshRequestParameters<'_> {
165
165
-
type Output = RefreshRequestParameters<'static>;
171
171
+
impl<S: BosStr + IntoStatic> IntoStatic for RefreshRequestParameters<S>
172
172
+
where
173
173
+
S::Output: BosStr,
174
174
+
{
175
175
+
type Output = RefreshRequestParameters<S::Output>;
166
176
167
177
fn into_static(self) -> Self::Output {
168
178
Self::Output {
169
179
grant_type: self.grant_type,
170
180
refresh_token: self.refresh_token.into_static(),
171
171
-
scope: self.scope.map(CowStr::into_static),
181
181
+
scope: self.scope.into_static(),
172
182
}
173
183
}
174
184
}
175
185
176
176
-
impl IntoStatic for ParParameters<'_> {
177
177
-
type Output = ParParameters<'static>;
186
186
+
impl<S: BosStr + IntoStatic> IntoStatic for ParParameters<S>
187
187
+
where
188
188
+
S::Output: BosStr,
189
189
+
{
190
190
+
type Output = ParParameters<S::Output>;
178
191
179
192
fn into_static(self) -> Self::Output {
180
193
Self::Output {
···
1
1
use super::response::OAuthTokenType;
2
2
+
use jacquard_common::IntoStatic;
3
3
+
use jacquard_common::bos::{BosStr, DefaultStr};
2
4
use jacquard_common::types::string::{Datetime, Did};
3
3
-
use jacquard_common::{CowStr, IntoStatic};
4
5
use serde::{Deserialize, Serialize};
5
6
6
7
/// A complete set of OAuth tokens and associated claims for an authenticated session.
···
9
10
/// everything it needs to make authorized requests. This is stored in the session
10
11
/// and refreshed transparently by `OAuthSession`.
11
12
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
12
12
-
pub struct TokenSet<'s> {
13
13
+
#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
14
14
+
pub struct TokenSet<S: BosStr = DefaultStr> {
13
15
/// The issuer URL of the authorization server that issued these tokens.
14
14
-
#[serde(borrow)]
15
15
-
pub iss: CowStr<'s>,
16
16
+
pub iss: S,
16
17
/// The subject DID identifying the authenticated user.
17
17
-
pub sub: Did<'s>,
18
18
+
pub sub: Did<S>,
18
19
/// The audience (resource server URL or DID) the tokens are intended for.
19
19
-
pub aud: CowStr<'s>,
20
20
+
pub aud: S,
20
21
/// The scopes granted by the authorization server.
21
21
-
pub scope: Option<CowStr<'s>>,
22
22
+
pub scope: Option<S>,
22
23
23
24
/// A refresh token that can be exchanged for new access tokens.
24
24
-
pub refresh_token: Option<CowStr<'s>>,
25
25
+
pub refresh_token: Option<S>,
25
26
/// The current access token to include in API requests.
26
26
-
pub access_token: CowStr<'s>,
27
27
+
pub access_token: S,
27
28
/// Whether the access token must be presented as a DPoP or Bearer token.
28
29
pub token_type: OAuthTokenType,
29
30
···
31
32
pub expires_at: Option<Datetime>,
32
33
}
33
34
34
34
-
impl IntoStatic for TokenSet<'_> {
35
35
-
type Output = TokenSet<'static>;
35
35
+
impl<S: BosStr> TokenSet<S> {
36
36
+
/// Convert to an `Nsid` with a different backing type.
37
37
+
pub fn convert<B: From<S> + BosStr>(self) -> TokenSet<B> {
38
38
+
TokenSet {
39
39
+
iss: self.iss.into(),
40
40
+
sub: self.sub.convert(),
41
41
+
aud: self.aud.into(),
42
42
+
scope: self.scope.map(|s| s.into()),
43
43
+
refresh_token: self.refresh_token.map(|t| t.into()),
44
44
+
access_token: self.access_token.into(),
45
45
+
token_type: self.token_type,
46
46
+
expires_at: self.expires_at,
47
47
+
}
48
48
+
}
49
49
+
}
50
50
+
51
51
+
impl<S: BosStr + IntoStatic> IntoStatic for TokenSet<S>
52
52
+
where
53
53
+
S::Output: BosStr,
54
54
+
{
55
55
+
type Output = TokenSet<S::Output>;
36
56
37
57
fn into_static(self) -> Self::Output {
38
58
TokenSet {
39
59
iss: self.iss.into_static(),
40
60
sub: self.sub.into_static(),
41
61
aud: self.aud.into_static(),
42
42
-
scope: self.scope.map(|s| s.into_static()),
43
43
-
refresh_token: self.refresh_token.map(|s| s.into_static()),
62
62
+
scope: self.scope.into_static(),
63
63
+
refresh_token: self.refresh_token.into_static(),
44
64
access_token: self.access_token.into_static(),
45
65
token_type: self.token_type,
46
46
-
expires_at: self.expires_at.map(|s| s.into_static()),
66
66
+
expires_at: self.expires_at.into_static(),
47
67
}
48
68
}
49
69
}
···
1
1
use base64::Engine;
2
2
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
3
3
use elliptic_curve::SecretKey;
4
4
-
use jacquard_common::CowStr;
4
4
+
use jacquard_common::BosStr;
5
5
use jose_jwk::{Key, crypto};
6
6
use rand::{CryptoRng, RngCore, rngs::ThreadRng};
7
7
use sha2::{Digest, Sha256};
8
8
+
use smol_str::SmolStr;
8
9
use std::cmp::Ordering;
9
10
10
11
use crate::{FALLBACK_ALG, types::OAuthAuthorizationServerMetadata};
···
13
14
/// supported, returning `None` if none are supported.
14
15
///
15
16
/// Currently only `ES256` (P-256 ECDSA) is implemented; other algorithm identifiers are skipped.
16
16
-
pub fn generate_key(allowed_algos: &[CowStr]) -> Option<Key> {
17
17
+
pub fn generate_key(allowed_algos: &[impl AsRef<str>]) -> Option<Key> {
17
18
for alg in allowed_algos {
18
19
#[allow(clippy::single_match)]
19
20
match alg.as_ref() {
···
31
32
}
32
33
33
34
/// Generate a cryptographically random 16-byte nonce encoded as base64url (no padding).
34
34
-
pub fn generate_nonce() -> CowStr<'static> {
35
35
+
pub fn generate_nonce() -> SmolStr {
35
36
URL_SAFE_NO_PAD
36
37
.encode(get_random_values::<_, 16>(&mut ThreadRng::default()))
37
38
.into()
38
39
}
39
40
40
41
/// Generate a cryptographically random 43-byte PKCE code verifier encoded as base64url (no padding).
41
41
-
pub fn generate_verifier() -> CowStr<'static> {
42
42
+
pub fn generate_verifier() -> SmolStr {
42
43
URL_SAFE_NO_PAD
43
44
.encode(get_random_values::<_, 43>(&mut ThreadRng::default()))
44
45
.into()
···
58
59
///
59
60
/// The ordering is: ES256K > ES (256 > 384 > 512) > PS (256 > 384 > 512) > RS (256 > 384 > 512) > other.
60
61
/// Algorithms within the same family are ordered by key length, preferring shorter (faster) keys first.
61
61
-
pub fn compare_algos(a: &CowStr, b: &CowStr) -> Ordering {
62
62
-
if a.as_ref() == "ES256K" {
62
62
+
pub fn compare_algos(a: &impl AsRef<str>, b: &impl AsRef<str>) -> Ordering {
63
63
+
let a = a.as_ref();
64
64
+
let b = b.as_ref();
65
65
+
if a == "ES256K" {
63
66
return Ordering::Less;
64
67
}
65
65
-
if b.as_ref() == "ES256K" {
68
68
+
if b == "ES256K" {
66
69
return Ordering::Greater;
67
70
}
68
71
for prefix in ["ES", "PS", "RS"] {
···
89
92
/// of the verifier, per [RFC 7636 §4.1](https://datatracker.ietf.org/doc/html/rfc7636#section-4.1).
90
93
/// The verifier must be kept secret and sent at the token endpoint; the challenge is sent at
91
94
/// the authorization endpoint.
92
92
-
pub fn generate_pkce() -> (CowStr<'static>, CowStr<'static>) {
95
95
+
pub fn generate_pkce() -> (SmolStr, SmolStr) {
93
96
// https://datatracker.ietf.org/doc/html/rfc7636#section-4.1
94
97
let verifier = generate_verifier();
95
98
(
96
99
URL_SAFE_NO_PAD
97
97
-
.encode(Sha256::digest(&verifier.as_str()))
100
100
+
.encode(Sha256::digest(verifier.as_str()))
98
101
.into(),
99
102
verifier,
100
103
)
···
105
108
/// Reads `dpop_signing_alg_values_supported` from the server metadata, sorts by preference
106
109
/// using [`compare_algos`], and attempts to generate a key for the most preferred supported
107
110
/// algorithm. Falls back to [`crate::FALLBACK_ALG`] if the server does not advertise any algorithms.
108
108
-
pub fn generate_dpop_key(metadata: &OAuthAuthorizationServerMetadata) -> Option<Key> {
109
109
-
let mut algs = metadata
111
111
+
pub fn generate_dpop_key<S: BosStr>(
112
112
+
metadata: &mut OAuthAuthorizationServerMetadata<S>,
113
113
+
) -> Option<Key> {
114
114
+
let mut fallback = vec![S::from_static(FALLBACK_ALG)];
115
115
+
let algs = metadata
110
116
.dpop_signing_alg_values_supported
111
111
-
.clone()
112
112
-
.unwrap_or(vec![FALLBACK_ALG.into()]);
117
117
+
.as_deref_mut()
118
118
+
.unwrap_or(&mut fallback);
113
119
algs.sort_by(compare_algos);
114
120
generate_key(&algs)
115
121
}
···
7
7
pub use jacquard_api::com_atproto::sync::subscribe_repos::Commit as FirehoseCommit;
8
8
pub use jacquard_api::com_atproto::sync::subscribe_repos::RepoOp;
9
9
use jacquard_api::com_atproto::sync::subscribe_repos::{Commit, RepoOpAction};
10
10
+
use jacquard_common::BosStr;
10
11
use jacquard_common::types::crypto::PublicKey;
12
12
+
use smol_str::SmolStr;
11
13
use smol_str::ToSmolStr;
12
14
13
15
/// Convert to VerifiedWriteOp for v1.1 validation
14
16
///
15
17
/// Validates that all required fields are present for inversion.
16
16
-
pub fn to_invertible_op(op: &RepoOp<'_>) -> Result<VerifiedWriteOp> {
18
18
+
pub fn to_invertible_op<S: BosStr + core::fmt::Display>(op: &RepoOp<S>) -> Result<VerifiedWriteOp> {
17
19
let key = op.path.to_smolstr();
18
20
match op.action {
19
21
RepoOpAction::Create => {
···
91
93
/// 4. Verify result matches commit.data (new MST root)
92
94
///
93
95
/// Returns the new MST root CID on success.
94
94
-
pub async fn validate_v1_0<S: BlockStore + Sync + 'static>(
95
95
-
fh_commit: &Commit<'_>,
96
96
+
pub async fn validate_v1_0<B: BosStr, S: BlockStore + Sync + 'static>(
97
97
+
fh_commit: &Commit<B>,
96
98
prev_mst_root: Option<IpldCid>,
97
99
prev_storage: Arc<S>,
98
100
pubkey: &PublicKey<'_>,
···
115
117
.await?
116
118
.ok_or_else(|| RepoError::not_found("commit block", &commit_cid))?;
117
119
118
118
-
let commit = super::Commit::from_cbor(&commit_bytes)?;
120
120
+
let commit = super::Commit::<SmolStr>::from_cbor(&commit_bytes)?;
119
121
120
122
// Verify DID matches
121
123
if commit.did().as_ref() != fh_commit.repo.as_ref() {
···
186
188
/// this is far from the most efficient possible validation function possible. The repo
187
189
/// tree struct carries extra information. However,
188
190
/// it has the virtue of making everything self-validating.
189
189
-
pub async fn validate_v1_1(fh_commit: &Commit<'_>, pubkey: &PublicKey<'_>) -> Result<IpldCid> {
191
191
+
pub async fn validate_v1_1<S: BosStr + core::fmt::Display>(
192
192
+
fh_commit: &Commit<S>,
193
193
+
pubkey: &PublicKey<'_>,
194
194
+
) -> Result<IpldCid> {
190
195
// 1. Require prev_data for v1.1
191
196
let prev_data_cid: IpldCid = fh_commit
192
197
.prev_data
···
210
215
.await?
211
216
.ok_or_else(|| RepoError::not_found("commit block", &commit_cid))?;
212
217
213
213
-
let commit = super::Commit::from_cbor(&commit_bytes)?;
218
218
+
let commit = super::Commit::<SmolStr>::from_cbor(&commit_bytes)?;
214
219
215
220
// Verify DID matches
216
221
if commit.did().as_ref() != fh_commit.repo.as_ref() {
···
300
305
record
301
306
}
302
307
303
303
-
async fn create_test_repo(storage: Arc<MemoryBlockStore>) -> Repository<MemoryBlockStore> {
308
308
+
async fn create_test_repo<'a>(
309
309
+
storage: Arc<MemoryBlockStore>,
310
310
+
) -> Repository<SmolStr, MemoryBlockStore> {
304
311
let did = Did::new("did:plc:test").unwrap();
305
312
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
306
313
···
332
339
let storage = Arc::new(MemoryBlockStore::new());
333
340
let mut repo = create_test_repo(storage.clone()).await;
334
341
335
335
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
336
336
-
let rkey = RecordKey(Rkey::new("test1").unwrap());
342
342
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
343
343
+
let rkey = RecordKey(Rkey::new("test1").unwrap().into_static());
337
344
338
338
-
let did = Did::new("did:plc:test").unwrap();
345
345
+
let did = Did::new("did:plc:test").unwrap().into_static();
339
346
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
340
347
let pubkey = get_public_key(&signing_key);
341
348
···
381
388
let storage = Arc::new(MemoryBlockStore::new());
382
389
let mut repo = create_test_repo(storage.clone()).await;
383
390
384
384
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
385
385
-
let rkey = RecordKey(Rkey::new("test1").unwrap());
391
391
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
392
392
+
let rkey = RecordKey(Rkey::new("test1").unwrap()).into_static();
386
393
387
387
-
let did = Did::new("did:plc:test").unwrap();
394
394
+
let did = Did::new("did:plc:test").unwrap().into_static();
388
395
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
389
396
let pubkey = get_public_key(&signing_key);
390
397
···
433
440
let storage = Arc::new(MemoryBlockStore::new());
434
441
let mut repo = create_test_repo(storage.clone()).await;
435
442
436
436
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
437
437
-
let did = Did::new("did:plc:test").unwrap();
443
443
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
444
444
+
let did = Did::new("did:plc:test").unwrap().into_static();
438
445
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
439
446
let pubkey = get_public_key(&signing_key);
440
447
···
442
449
let ops1 = vec![
443
450
RecordWriteOp::Create {
444
451
collection: collection.clone(),
445
445
-
rkey: RecordKey(Rkey::new("post1").unwrap()),
452
452
+
rkey: RecordKey(Rkey::new("post1").unwrap()).into_static(),
446
453
record: make_test_record(1),
447
454
},
448
455
RecordWriteOp::Create {
449
456
collection: collection.clone(),
450
450
-
rkey: RecordKey(Rkey::new("post2").unwrap()),
457
457
+
rkey: RecordKey(Rkey::new("post2").unwrap()).into_static(),
451
458
record: make_test_record(2),
452
459
},
453
460
];
···
476
483
let storage = Arc::new(MemoryBlockStore::new());
477
484
let mut repo = create_test_repo(storage.clone()).await;
478
485
479
479
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
480
480
-
let did = Did::new("did:plc:test").unwrap();
486
486
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
487
487
+
let did = Did::new("did:plc:test").unwrap().into_static();
481
488
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
482
489
let pubkey = get_public_key(&signing_key);
483
490
484
491
// First: create records
485
485
-
let rkey1 = RecordKey(Rkey::new("post1").unwrap());
486
486
-
let rkey2 = RecordKey(Rkey::new("post2").unwrap());
492
492
+
let rkey1 = RecordKey(Rkey::new("post1").unwrap()).into_static();
493
493
+
let rkey2 = RecordKey(Rkey::new("post2").unwrap()).into_static();
487
494
488
495
let create_ops = vec![
489
496
RecordWriteOp::Create {
···
552
559
let storage = Arc::new(MemoryBlockStore::new());
553
560
let mut repo = create_test_repo(storage.clone()).await;
554
561
555
555
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
556
556
-
let did = Did::new("did:plc:test").unwrap();
562
562
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
563
563
+
let did = Did::new("did:plc:test").unwrap().into_static();
557
564
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
558
565
let pubkey = get_public_key(&signing_key);
559
566
560
567
let ops = vec![RecordWriteOp::Create {
561
568
collection: collection.clone(),
562
562
-
rkey: RecordKey(Rkey::new("test1").unwrap()),
569
569
+
rkey: RecordKey(Rkey::new("test1").unwrap()).into_static(),
563
570
record: make_test_record(1),
564
571
}];
565
572
···
607
614
let storage = Arc::new(MemoryBlockStore::new());
608
615
let mut repo = create_test_repo(storage.clone()).await;
609
616
610
610
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
611
611
-
let did = Did::new("did:plc:test").unwrap();
617
617
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
618
618
+
let did = Did::new("did:plc:test").unwrap().into_static();
612
619
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
613
620
let pubkey = get_public_key(&signing_key);
614
621
···
616
623
let ops = vec![
617
624
RecordWriteOp::Create {
618
625
collection: collection.clone(),
619
619
-
rkey: RecordKey(Rkey::new("aaa").unwrap()),
626
626
+
rkey: RecordKey(Rkey::new("aaa").unwrap()).into_static(),
620
627
record: make_test_record(1),
621
628
},
622
629
RecordWriteOp::Create {
623
630
collection: collection.clone(),
624
624
-
rkey: RecordKey(Rkey::new("zzz").unwrap()),
631
631
+
rkey: RecordKey(Rkey::new("zzz").unwrap()).into_static(),
625
632
record: make_test_record(2),
626
633
},
627
634
];
···
669
676
let storage = Arc::new(MemoryBlockStore::new());
670
677
let mut repo = create_test_repo(storage.clone()).await;
671
678
672
672
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
673
673
-
let did = Did::new("did:plc:test").unwrap();
679
679
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
680
680
+
let did = Did::new("did:plc:test").unwrap().into_static();
674
681
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
675
682
let pubkey = get_public_key(&signing_key);
676
683
677
684
let ops = vec![RecordWriteOp::Create {
678
685
collection: collection.clone(),
679
679
-
rkey: RecordKey(Rkey::new("test1").unwrap()),
686
686
+
rkey: RecordKey(Rkey::new("test1").unwrap()).into_static(),
680
687
record: make_test_record(1),
681
688
}];
682
689
···
730
737
let storage = Arc::new(MemoryBlockStore::new());
731
738
let mut repo = create_test_repo(storage.clone()).await;
732
739
733
733
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
734
734
-
let did = Did::new("did:plc:test").unwrap();
735
735
-
let wrong_did = Did::new("did:plc:wrong").unwrap();
740
740
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
741
741
+
let did = Did::new("did:plc:test").unwrap().into_static();
742
742
+
let wrong_did = Did::new("did:plc:wrong").unwrap().into_static();
736
743
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
737
744
let pubkey = get_public_key(&signing_key);
738
745
739
746
let ops = vec![RecordWriteOp::Create {
740
747
collection: collection.clone(),
741
741
-
rkey: RecordKey(Rkey::new("test1").unwrap()),
748
748
+
rkey: RecordKey(Rkey::new("test1").unwrap()).into_static(),
742
749
record: make_test_record(1),
743
750
}];
744
751
···
778
785
let storage = Arc::new(MemoryBlockStore::new());
779
786
let mut repo = create_test_repo(storage.clone()).await;
780
787
781
781
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
782
782
-
let did = Did::new("did:plc:test").unwrap();
788
788
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
789
789
+
let did = Did::new("did:plc:test").unwrap().into_static();
783
790
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
784
791
785
792
// Use a different key for verification
···
788
795
789
796
let ops = vec![RecordWriteOp::Create {
790
797
collection: collection.clone(),
791
791
-
rkey: RecordKey(Rkey::new("test1").unwrap()),
798
798
+
rkey: RecordKey(Rkey::new("test1").unwrap().into_static()),
792
799
record: make_test_record(1),
793
800
}];
794
801
···
819
826
let storage = Arc::new(MemoryBlockStore::new());
820
827
let mut repo = create_test_repo(storage.clone()).await;
821
828
822
822
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
823
823
-
let did = Did::new("did:plc:test").unwrap();
829
829
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
830
830
+
let did = Did::new("did:plc:test").unwrap().into_static();
824
831
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
825
832
let pubkey = get_public_key(&signing_key);
826
833
827
834
let ops = vec![RecordWriteOp::Create {
828
835
collection: collection.clone(),
829
829
-
rkey: RecordKey(Rkey::new("test1").unwrap()),
836
836
+
rkey: RecordKey(Rkey::new("test1").unwrap().into_static()),
830
837
record: make_test_record(1),
831
838
}];
832
839
···
866
873
let storage = Arc::new(MemoryBlockStore::new());
867
874
let mut repo = create_test_repo(storage.clone()).await;
868
875
869
869
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
870
870
-
let did = Did::new("did:plc:test").unwrap();
876
876
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
877
877
+
let did = Did::new("did:plc:test").unwrap().into_static();
871
878
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
872
879
let pubkey = get_public_key(&signing_key);
873
880
874
881
let ops = vec![RecordWriteOp::Create {
875
882
collection: collection.clone(),
876
876
-
rkey: RecordKey(Rkey::new("test1").unwrap()),
883
883
+
rkey: RecordKey(Rkey::new("test1").unwrap()).into_static(),
877
884
record: make_test_record(1),
878
885
}];
879
886
···
8
8
use crate::error::{CommitError, Result};
9
9
use bytes::Bytes;
10
10
use cid::Cid as IpldCid;
11
11
-
use jacquard_common::IntoStatic;
12
11
use jacquard_common::types::crypto::PublicKey;
13
12
use jacquard_common::types::string::Did;
14
13
use jacquard_common::types::tid::Tid;
14
14
+
use jacquard_common::{BosStr, IntoStatic};
15
15
16
16
/// Repository commit object
17
17
///
···
22
22
/// serialized (v2 uses it, v3 must include it even if null). This struct
23
23
/// handles both by always including `prev` in serialization.
24
24
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
25
25
-
pub struct Commit<'a> {
25
25
+
pub struct Commit<S: BosStr> {
26
26
/// Repository DID
27
27
-
#[serde(borrow)]
28
28
-
pub did: Did<'a>,
27
27
+
pub did: Did<S>,
29
28
30
29
/// Commit version (2 or 3)
31
30
pub version: i64,
···
48
47
/// Explicitly a separate struct minus the sig field to match deserialization/signatures
49
48
/// from other implementations.
50
49
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
51
51
-
pub struct UnsignedCommit<'a> {
50
50
+
pub struct UnsignedCommit<S: BosStr> {
52
51
/// Repository DID
53
53
-
#[serde(borrow)]
54
54
-
pub did: Did<'a>,
52
52
+
pub did: Did<S>,
55
53
56
54
/// Commit version (2 or 3)
57
55
pub version: i64,
···
66
64
pub prev: Option<IpldCid>,
67
65
}
68
66
69
69
-
impl<'a> UnsignedCommit<'a> {
67
67
+
impl<S: BosStr + serde::Serialize> UnsignedCommit<S> {
70
68
/// Create new unsigned commit (version = 3, sig empty)
71
69
pub fn new_unsigned(
72
72
-
did: Did<'a>,
70
70
+
did: Did<S>,
73
71
data: IpldCid,
74
72
rev: Tid,
75
73
prev: Option<IpldCid>,
76
76
-
) -> UnsignedCommit<'a> {
74
74
+
) -> UnsignedCommit<S> {
77
75
Self {
78
76
did,
79
77
version: 3,
···
85
83
/// Get unsigned commit bytes (for signing/verification)
86
84
pub(super) fn unsigned_bytes(&self) -> Result<Vec<u8>> {
87
85
// Serialize without signature field
88
88
-
let unsigned = self.clone();
89
89
-
serde_ipld_dagcbor::to_vec(&unsigned)
86
86
+
serde_ipld_dagcbor::to_vec(self)
90
87
.map_err(|e| crate::error::CommitError::Serialization(Box::new(e)).into())
91
88
}
92
89
93
90
/// Sign this commit with a key
94
94
-
pub fn sign(self, key: &impl SigningKey) -> Result<Commit<'a>> {
91
91
+
pub fn sign(self, key: &impl SigningKey) -> Result<Commit<S>> {
95
92
let unsigned = self.unsigned_bytes()?;
96
93
let sig = key.sign_bytes(&unsigned)?;
97
94
···
105
102
})
106
103
}
107
104
/// Get the repository DID
108
108
-
pub fn did(&self) -> &Did<'a> {
105
105
+
pub fn did(&self) -> &Did<S> {
109
106
&self.did
110
107
}
111
108
···
125
122
}
126
123
}
127
124
128
128
-
impl<'a> Commit<'a> {
125
125
+
impl<S: BosStr + Clone + serde::Serialize> Commit<S> {
129
126
/// Create new unsigned commit (version = 3, sig empty)
130
127
pub fn new_unsigned(
131
131
-
did: Did<'a>,
128
128
+
did: Did<S>,
132
129
data: IpldCid,
133
130
rev: Tid,
134
131
prev: Option<IpldCid>,
135
135
-
) -> UnsignedCommit<'a> {
132
132
+
) -> UnsignedCommit<S> {
136
133
UnsignedCommit {
137
134
did,
138
135
version: 3,
···
157
154
}
158
155
159
156
/// Get the repository DID
160
160
-
pub fn did(&self) -> &Did<'a> {
157
157
+
pub fn did(&self) -> &Did<S> {
161
158
&self.did
162
159
}
163
160
···
187
184
}
188
185
189
186
/// Deserialize from DAG-CBOR
190
190
-
pub fn from_cbor(data: &'a [u8]) -> Result<Self> {
187
187
+
pub fn from_cbor<'a>(data: &'a [u8]) -> Result<Self>
188
188
+
where
189
189
+
S: serde::Deserialize<'a>,
190
190
+
{
191
191
serde_ipld_dagcbor::from_slice(data)
192
192
.map_err(|e| CommitError::Serialization(Box::new(e)).into())
193
193
}
···
251
251
}
252
252
}
253
253
254
254
-
impl IntoStatic for Commit<'_> {
255
255
-
type Output = Commit<'static>;
254
254
+
impl<S> IntoStatic for Commit<S>
255
255
+
where
256
256
+
S: BosStr + IntoStatic,
257
257
+
S::Output: BosStr,
258
258
+
{
259
259
+
type Output = Commit<S::Output>;
256
260
257
261
fn into_static(self) -> Self::Output {
258
262
Commit {
···
22
22
use crate::mst::Mst;
23
23
use crate::storage::MemoryBlockStore;
24
24
use cid::Cid as IpldCid;
25
25
-
use jacquard_common::CowStr;
26
25
use jacquard_common::types::string::Did;
27
27
-
use smol_str::format_smolstr;
26
26
+
use jacquard_common::{BosStr, CowStr};
27
27
+
use smol_str::{SmolStr, format_smolstr};
28
28
use std::sync::Arc;
29
29
30
30
/// A claim about a record's CID at a specific path
···
88
88
/// let result = verify_proofs(car_bytes, claims, did, pubkey).await?;
89
89
/// assert_eq!(result.verified.len(), 2); // Both claims verified
90
90
/// ```
91
91
-
pub async fn verify_proofs<'a>(
91
91
+
pub async fn verify_proofs<'a, 'de, S>(
92
92
car_bytes: &[u8],
93
93
claims: Vec<RecordClaim<'a>>,
94
94
-
did: &Did<'_>,
94
94
+
did: &Did<S>,
95
95
pubkey: &jacquard_common::types::crypto::PublicKey<'_>,
96
96
-
) -> Result<VerifyProofsOutput<'a>, ProofError> {
96
96
+
) -> Result<VerifyProofsOutput<'a>, ProofError>
97
97
+
where
98
98
+
S: BosStr + Clone + serde::Serialize + serde::Deserialize<'de>,
99
99
+
{
97
100
// 1. Parse CAR file
98
101
let parsed =
99
102
crate::car::parse_car_bytes(car_bytes)
···
113
116
.map_err(|_| ProofError::CommitNotFound)?
114
117
.ok_or(ProofError::CommitNotFound)?;
115
118
116
116
-
let commit = super::Commit::from_cbor(&commit_bytes).map_err(|e| {
119
119
+
let commit = super::Commit::<SmolStr>::from_cbor(&commit_bytes).map_err(|e| {
117
120
ProofError::CommitDeserializeFailed {
118
121
source: Box::new(e),
119
122
}
···
1
1
//! MST diff calculation
2
2
3
3
use std::collections::BTreeMap;
4
4
+
use std::convert::{From, Infallible};
4
5
use std::future::Future;
5
6
use std::pin::Pin;
7
7
+
use std::str::FromStr;
6
8
7
9
use super::cursor::{CursorPosition, MstCursor};
8
10
use super::tree::Mst;
···
13
15
use crate::storage::BlockStore;
14
16
use bytes::Bytes;
15
17
use cid::Cid as IpldCid;
18
18
+
use jacquard_api::com_atproto::sync::subscribe_repos::RepoOpAction;
19
19
+
use jacquard_common::BosStr;
16
20
use jacquard_common::types::cid::CidLink;
17
21
use smol_str::SmolStr;
18
22
···
160
164
///
161
165
/// Returns operations in the format used by `com.atproto.sync.subscribeRepos`.
162
166
/// All update/delete operations include prev CIDs for sync v1.1 validation.
163
163
-
pub fn to_repo_ops(&self) -> Vec<RepoOp<'_>> {
167
167
+
pub fn to_repo_ops<'s, S: BosStr + FromStr<Err = Infallible>>(&'s self) -> Vec<RepoOp<S>> {
164
168
let mut ops = Vec::with_capacity(self.op_count());
165
169
166
170
// Add creates
167
171
for (key, cid) in &self.creates {
168
172
ops.push(RepoOp {
169
169
-
action: "create".into(),
170
170
-
path: key.as_str().into(),
173
173
+
action: RepoOpAction::from_value(S::from_static("create")),
174
174
+
path: S::from_str(key.as_str()).unwrap(),
171
175
cid: Some(CidLink::from(*cid)),
172
176
prev: None,
173
177
extra_data: None,
···
177
181
// Add updates
178
182
for (key, new_cid, old_cid) in &self.updates {
179
183
ops.push(RepoOp {
180
180
-
action: "update".into(),
181
181
-
path: key.as_str().into(),
184
184
+
action: RepoOpAction::from_value(S::from_static("update")),
185
185
+
path: S::from_str(key.as_str()).unwrap(),
182
186
cid: Some(CidLink::from(*new_cid)),
183
187
prev: Some(CidLink::from(*old_cid)),
184
188
extra_data: None,
···
188
192
// Add deletes
189
193
for (key, old_cid) in &self.deletes {
190
194
ops.push(RepoOp {
191
191
-
action: "delete".into(),
192
192
-
path: key.as_str().into(),
195
195
+
action: RepoOpAction::from_value(S::from_static("delete")),
196
196
+
path: S::from_str(key.as_str()).unwrap(),
193
197
cid: None, // null for deletes
194
198
prev: Some(CidLink::from(*old_cid)),
195
199
extra_data: None,
···
8
8
use bytes::Bytes;
9
9
use cid::Cid as IpldCid;
10
10
use core::fmt;
11
11
+
use jacquard_common::BosStr;
11
12
use jacquard_common::types::recordkey::Rkey;
12
13
use jacquard_common::types::string::{Nsid, RecordKey};
13
14
use jacquard_common::types::value::RawData;
···
63
64
/// needs to be serialized and stored. The data is a generic IPLD map
64
65
/// (similar to rsky's `RepoRecord = BTreeMap<String, Lex>`).
65
66
#[derive(Debug, Clone, PartialEq)]
66
66
-
pub enum RecordWriteOp<'a> {
67
67
+
pub enum RecordWriteOp<'a, S: BosStr> {
67
68
/// Create new record with data
68
69
Create {
69
70
/// Collection NSID
70
70
-
collection: Nsid<'a>,
71
71
+
collection: Nsid<S>,
71
72
/// Record key
72
72
-
rkey: RecordKey<Rkey<'a>>,
73
73
+
rkey: RecordKey<Rkey<S>>,
73
74
/// Record data (will be serialized to DAG-CBOR and CID computed)
74
75
record: std::collections::BTreeMap<SmolStr, RawData<'a>>,
75
76
},
···
77
78
/// Update existing record with new data
78
79
Update {
79
80
/// Collection NSID
80
80
-
collection: Nsid<'a>,
81
81
+
collection: Nsid<S>,
81
82
/// Record key
82
82
-
rkey: RecordKey<Rkey<'a>>,
83
83
+
rkey: RecordKey<Rkey<S>>,
83
84
/// New record data
84
85
record: std::collections::BTreeMap<SmolStr, RawData<'a>>,
85
86
/// Previous CID (optional for validation)
···
89
90
/// Delete record
90
91
Delete {
91
92
/// Collection NSID
92
92
-
collection: Nsid<'a>,
93
93
+
collection: Nsid<S>,
93
94
/// Record key
94
94
-
rkey: RecordKey<Rkey<'a>>,
95
95
+
rkey: RecordKey<Rkey<S>>,
95
96
/// Previous CID (optional for validation)
96
97
prev: Option<IpldCid>,
97
98
},
98
99
}
99
100
100
100
-
impl<'a> RecordWriteOp<'a> {
101
101
+
impl<'a, S: BosStr> RecordWriteOp<'a, S> {
101
102
/// Get the collection NSID for this operation
102
102
-
pub fn collection(&self) -> &Nsid<'a> {
103
103
+
pub fn collection(&self) -> &Nsid<S> {
103
104
match self {
104
105
RecordWriteOp::Create { collection, .. } => collection,
105
106
RecordWriteOp::Update { collection, .. } => collection,
···
108
109
}
109
110
110
111
/// Get the record key for this operation
111
111
-
pub fn rkey(&self) -> &RecordKey<Rkey<'a>> {
112
112
+
pub fn rkey(&self) -> &RecordKey<Rkey<S>> {
112
113
match self {
113
114
RecordWriteOp::Create { rkey, .. } => rkey,
114
115
RecordWriteOp::Update { rkey, .. } => rkey,
···
10
10
use crate::storage::BlockStore;
11
11
use bytes::Bytes;
12
12
use cid::Cid as IpldCid;
13
13
-
use jacquard_common::IntoStatic;
13
13
+
use jacquard_common::BosStr;
14
14
use jacquard_common::types::cid::CidLink;
15
15
use jacquard_common::types::recordkey::RecordKeyType;
16
16
use jacquard_common::types::string::{Datetime, Did, Nsid, RecordKey, Tid};
17
17
use jacquard_common::types::tid::Ticker;
18
18
use smol_str::format_smolstr;
19
19
use std::collections::BTreeMap;
20
20
+
use std::convert::Infallible;
20
21
use std::fmt::{self, Display, Formatter};
21
22
use std::path::Path;
23
23
+
use std::str::FromStr;
22
24
use std::sync::Arc;
23
25
24
26
/// Commit data for repository updates
···
65
67
///
66
68
/// Converts this commit into a `FirehoseCommit` with `prev_data` field
67
69
/// and relevant blocks for inductive validation.
68
68
-
pub async fn to_firehose_commit(
70
70
+
pub async fn to_firehose_commit<S: BosStr + Clone>(
69
71
&self,
70
70
-
repo: &Did<'_>,
72
72
+
repo: &Did<S>,
71
73
seq: i64,
72
74
time: Datetime,
73
73
-
ops: Vec<RepoOp<'static>>,
74
74
-
blobs: Vec<CidLink<'static>>,
75
75
-
) -> Result<FirehoseCommit<'static>> {
75
75
+
ops: Vec<RepoOp<S>>,
76
76
+
blobs: Vec<CidLink<S>>,
77
77
+
) -> Result<FirehoseCommit<S>> {
76
78
let mut proof_blocks = self.blocks.clone();
77
79
proof_blocks.append(&mut self.relevant_blocks.clone());
78
80
// Convert relevant blocks to CAR format
79
81
let blocks_car = crate::car::write_car_bytes(self.cid, proof_blocks).await?;
80
82
81
83
Ok(FirehoseCommit {
82
82
-
repo: repo.clone().into_static(),
84
84
+
repo: repo.clone(),
83
85
rev: self.rev.clone(),
84
86
seq,
85
87
since: Some(self.since.clone().unwrap_or_else(|| self.rev.clone())),
···
124
126
/// # Ok(())
125
127
/// # }
126
128
/// ```
127
127
-
pub struct Repository<S: BlockStore> {
128
128
-
mst: Mst<S>,
129
129
-
storage: Arc<S>,
130
130
-
commit: Commit<'static>,
129
129
+
pub struct Repository<S: BosStr, BS: BlockStore> {
130
130
+
mst: Mst<BS>,
131
131
+
storage: Arc<BS>,
132
132
+
commit: Commit<S>,
131
133
commit_cid: IpldCid,
132
134
}
133
135
134
134
-
impl<S: BlockStore + Sync + 'static> Repository<S> {
136
136
+
impl<S, BS: BlockStore + Sync + 'static> Repository<S, BS>
137
137
+
where
138
138
+
S: BosStr + Clone + serde::Serialize + serde::de::DeserializeOwned + FromStr<Err = Infallible>,
139
139
+
{
135
140
/// Create repository from existing components
136
141
///
137
142
/// Static constructor for when you already have the MST, commit, and CID.
138
138
-
pub fn new(storage: Arc<S>, mst: Mst<S>, commit: Commit<'static>, commit_cid: IpldCid) -> Self {
143
143
+
pub fn new(storage: Arc<BS>, mst: Mst<BS>, commit: Commit<S>, commit_cid: IpldCid) -> Self {
139
144
Self {
140
145
storage,
141
146
mst,
···
145
150
}
146
151
147
152
/// Load repository from commit CID
148
148
-
pub async fn from_commit(storage: Arc<S>, commit_cid: &IpldCid) -> Result<Self> {
153
153
+
pub async fn from_commit(storage: Arc<BS>, commit_cid: &IpldCid) -> Result<Self> {
149
154
let commit_bytes = storage
150
155
.get(commit_cid)
151
156
.await?
···
154
159
.with_help("Commit must be applied to storage before loading repository - use apply_commit() or ensure commit is persisted")
155
160
})?;
156
161
157
157
-
let commit = Commit::from_cbor(&commit_bytes)?;
162
162
+
let commit = Commit::<S>::from_cbor(&commit_bytes)?;
158
163
let mst_root = commit.data();
159
164
160
165
let mst = Mst::load(storage.clone(), *mst_root, None);
···
162
167
Ok(Self {
163
168
mst,
164
169
storage,
165
165
-
commit: commit.into_static(),
170
170
+
commit: commit,
166
171
commit_cid: *commit_cid,
167
172
})
168
173
}
···
174
179
///
175
180
/// This does NOT persist to storage - use `create_from_commit` or `create` for that.
176
181
pub async fn format_init_commit<K>(
177
177
-
storage: Arc<S>,
178
178
-
did: Did<'static>,
182
182
+
storage: Arc<BS>,
183
183
+
did: Did<S>,
179
184
signing_key: &K,
180
180
-
initial_writes: Option<&[RecordWriteOp<'_>]>,
185
185
+
initial_writes: Option<&[RecordWriteOp<'_, S>]>,
181
186
) -> Result<CommitData>
182
187
where
183
188
K: SigningKey,
···
240
245
/// Create repository from CommitData
241
246
///
242
247
/// Applies the commit to storage and loads the repository from it.
243
243
-
pub async fn create_from_commit(storage: Arc<S>, commit_data: CommitData) -> Result<Self> {
248
248
+
pub async fn create_from_commit(storage: Arc<BS>, commit_data: CommitData) -> Result<Self> {
244
249
let commit_cid = commit_data.cid;
245
250
storage.apply_commit(commit_data).await?;
246
251
Self::from_commit(storage, &commit_cid).await
···
250
255
///
251
256
/// Convenience method that formats an initial commit and applies it to storage.
252
257
pub async fn create<K>(
253
253
-
storage: Arc<S>,
254
254
-
did: Did<'static>,
258
258
+
storage: Arc<BS>,
259
259
+
did: Did<S>,
255
260
signing_key: &K,
256
256
-
initial_writes: Option<&[RecordWriteOp<'_>]>,
261
261
+
initial_writes: Option<&[RecordWriteOp<'_, S>]>,
257
262
) -> Result<Self>
258
263
where
259
264
K: SigningKey,
···
266
271
/// Get a record by collection and rkey
267
272
pub async fn get_record<T: RecordKeyType>(
268
273
&self,
269
269
-
collection: &Nsid<'_>,
274
274
+
collection: &Nsid<S>,
270
275
rkey: &RecordKey<T>,
271
276
) -> Result<Option<IpldCid>> {
272
277
let key = format!("{}/{}", collection.as_ref(), rkey.as_ref());
···
276
281
/// Create a record (error if exists)
277
282
pub async fn create_record<T: RecordKeyType>(
278
283
&mut self,
279
279
-
collection: &Nsid<'_>,
284
284
+
collection: &Nsid<S>,
280
285
rkey: &RecordKey<T>,
281
286
record_cid: IpldCid,
282
287
) -> Result<()> {
···
293
298
/// Update a record (error if not exists, returns previous CID)
294
299
pub async fn update_record<T: RecordKeyType>(
295
300
&mut self,
296
296
-
collection: &Nsid<'_>,
301
301
+
collection: &Nsid<S>,
297
302
rkey: &RecordKey<T>,
298
303
record_cid: IpldCid,
299
304
) -> Result<IpldCid> {
···
312
317
/// Delete a record (error if not exists, returns deleted CID)
313
318
pub async fn delete_record<T: RecordKeyType>(
314
319
&mut self,
315
315
-
collection: &Nsid<'_>,
320
320
+
collection: &Nsid<S>,
316
321
rkey: &RecordKey<T>,
317
322
) -> Result<IpldCid> {
318
323
let key = format!("{}/{}", collection.as_ref(), rkey.as_ref());
···
359
364
/// Returns `(ops, CommitData)` - ops are needed for `to_firehose_commit()`.
360
365
pub async fn create_commit<K>(
361
366
&mut self,
362
362
-
ops: &[RecordWriteOp<'_>],
363
363
-
did: &Did<'_>,
367
367
+
ops: &[RecordWriteOp<'_, S>],
368
368
+
did: &Did<S>,
364
369
prev: Option<IpldCid>,
365
370
signing_key: &K,
366
366
-
) -> Result<(Vec<RepoOp<'static>>, CommitData)>
371
371
+
) -> Result<(Vec<RepoOp<S>>, CommitData)>
367
372
where
368
373
K: SigningKey,
374
374
+
S: FromStr<Err = Infallible>,
369
375
{
370
376
// Step 1: Apply all write operations to build new MST and collect leaf blocks
371
377
let mut updated_tree = self.mst.clone();
···
463
469
let diff = self.mst.diff(&updated_tree).await?;
464
470
465
471
// Step 3: Extract everything we need from diff
466
466
-
let repo_ops = diff
467
467
-
.to_repo_ops()
468
468
-
.into_iter()
469
469
-
.map(|op| op.into_static())
470
470
-
.collect();
472
472
+
let repo_ops = diff.to_repo_ops().into_iter().collect();
471
473
472
474
// Step 4: Build blocks and relevant_blocks collections using diff tracking
473
475
//
···
506
508
507
509
// Step 5: Create and sign commit
508
510
let rev = Ticker::new().next(Some(self.commit.rev.clone()));
509
509
-
let commit = Commit::new_unsigned(did.clone().into_static(), data, rev.clone(), prev)
510
510
-
.sign(signing_key)?;
511
511
+
let commit =
512
512
+
Commit::new_unsigned(did.clone(), data, rev.clone(), prev).sign(signing_key)?;
511
513
512
514
let commit_cbor = commit.to_cbor()?;
513
515
let commit_cid = crate::mst::util::compute_cid(&commit_cbor)?;
···
555
557
RepoError::not_found("commit block", &commit_cid)
556
558
.with_help("Commit block should have been persisted by apply_commit() - this indicates a storage inconsistency")
557
559
})?;
558
558
-
let commit = Commit::from_cbor(&commit_bytes)?;
560
560
+
let commit = Commit::<S>::from_cbor(&commit_bytes)?;
559
561
560
560
-
self.commit = commit.into_static();
562
562
+
self.commit = commit;
561
563
self.commit_cid = commit_cid;
562
564
563
565
// Reload MST from new root
···
573
575
/// record operations (e.g., `create_record()`, `update_record()`, `delete_record()`).
574
576
pub async fn commit<K>(
575
577
&mut self,
576
576
-
did: &Did<'_>,
578
578
+
did: &Did<S>,
577
579
prev: Option<IpldCid>,
578
580
signing_key: &K,
579
579
-
) -> Result<(Vec<RepoOp<'static>>, IpldCid)>
581
581
+
) -> Result<(Vec<RepoOp<S>>, IpldCid)>
580
582
where
581
583
K: SigningKey,
582
584
{
···
590
592
}
591
593
592
594
/// Get the underlying MST
593
593
-
pub fn mst(&self) -> &Mst<S> {
595
595
+
pub fn mst(&self) -> &Mst<BS> {
594
596
&self.mst
595
597
}
596
598
597
599
/// Get reference to the storage
598
598
-
pub fn storage(&self) -> &Arc<S> {
600
600
+
pub fn storage(&self) -> &Arc<BS> {
599
601
&self.storage
600
602
}
601
603
602
604
/// Get the current commit
603
603
-
pub fn current_commit(&self) -> &Commit<'static> {
605
605
+
pub fn current_commit(&self) -> &Commit<S> {
604
606
&self.commit
605
607
}
606
608
···
610
612
}
611
613
612
614
/// Get the DID from the current commit
613
613
-
pub fn did(&self) -> &Did<'_> {
615
615
+
pub fn did(&self) -> &Did<S> {
614
616
self.commit.did()
615
617
}
616
618
}
617
619
618
618
-
impl<S: BlockStore> Display for Repository<S> {
620
620
+
impl<S: BosStr, BS: BlockStore> Display for Repository<S, BS> {
619
621
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
620
622
use crate::mst::tree::short_cid;
621
623
622
624
writeln!(f, "Repository {{")?;
623
623
-
writeln!(f, " DID: {}", self.commit.did())?;
625
625
+
writeln!(f, " DID: {}", self.commit.did)?;
624
626
writeln!(f, " Commit: {}", short_cid(&self.commit_cid))?;
625
627
writeln!(f, " Rev: {}", self.commit.rev)?;
626
626
-
writeln!(f, " Data: {}", short_cid(self.commit.data()))?;
628
628
+
writeln!(f, " Data: {}", short_cid(&self.commit.data))?;
627
629
writeln!(f, " MST:")?;
628
630
629
631
// Format MST with indentation
···
642
644
643
645
use super::*;
644
646
use crate::storage::MemoryBlockStore;
645
645
-
use jacquard_common::types::{
646
646
-
crypto::{KeyCodec, PublicKey},
647
647
-
recordkey::Rkey,
648
648
-
value::RawData,
647
647
+
use jacquard_common::{
648
648
+
IntoStatic,
649
649
+
types::{
650
650
+
crypto::{KeyCodec, PublicKey},
651
651
+
recordkey::Rkey,
652
652
+
value::RawData,
653
653
+
},
649
654
};
650
655
use smol_str::SmolStr;
651
656
···
676
681
record
677
682
}
678
683
679
679
-
async fn create_test_repo(storage: Arc<MemoryBlockStore>) -> Repository<MemoryBlockStore> {
684
684
+
async fn create_test_repo(
685
685
+
storage: Arc<MemoryBlockStore>,
686
686
+
) -> Repository<SmolStr, MemoryBlockStore> {
680
687
let did = Did::new("did:plc:test").unwrap();
681
688
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
682
689
···
701
708
let storage = Arc::new(MemoryBlockStore::new());
702
709
let mut repo = create_test_repo(storage.clone()).await;
703
710
704
704
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
705
705
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
711
711
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
712
712
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
706
713
707
714
let ops = vec![RecordWriteOp::Create {
708
715
collection: collection.clone().into_static(),
···
710
717
record: make_test_record(1),
711
718
}];
712
719
713
713
-
let did = Did::new("did:plc:test").unwrap();
720
720
+
let did = Did::new("did:plc:test").unwrap().into_static();
714
721
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
715
722
let (repo_ops, commit_data) = repo
716
723
.create_commit(
···
738
745
let storage = Arc::new(MemoryBlockStore::new());
739
746
let mut repo = create_test_repo(storage).await;
740
747
741
741
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
742
742
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
748
748
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
749
749
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
743
750
let cid = make_test_cid(1);
744
751
745
752
repo.create_record(&collection, &rkey, cid).await.unwrap();
···
755
762
let storage = Arc::new(MemoryBlockStore::new());
756
763
let mut repo = create_test_repo(storage).await;
757
764
758
758
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
759
759
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
765
765
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
766
766
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
760
767
let cid1 = make_test_cid(1);
761
768
let cid2 = make_test_cid(2);
762
769
···
774
781
let storage = Arc::new(MemoryBlockStore::new());
775
782
let mut repo = create_test_repo(storage).await;
776
783
777
777
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
778
778
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
784
784
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
785
785
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
779
786
let cid = make_test_cid(1);
780
787
781
788
let result = repo.update_record(&collection, &rkey, cid).await;
···
787
794
let storage = Arc::new(MemoryBlockStore::new());
788
795
let mut repo = create_test_repo(storage).await;
789
796
790
790
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
791
791
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
797
797
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
798
798
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
792
799
let cid = make_test_cid(1);
793
800
794
801
repo.create_record(&collection, &rkey, cid).await.unwrap();
···
805
812
let storage = Arc::new(MemoryBlockStore::new());
806
813
let mut repo = create_test_repo(storage).await;
807
814
808
808
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
809
809
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
815
815
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
816
816
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
810
817
811
818
let result = repo.delete_record(&collection, &rkey).await;
812
819
assert!(result.is_err());
···
817
824
let storage = Arc::new(MemoryBlockStore::new());
818
825
let mut repo = create_test_repo(storage.clone()).await;
819
826
820
820
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
821
821
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
827
827
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
828
828
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
822
829
let cid = make_test_cid(1);
823
830
824
831
repo.create_record(&collection, &rkey, cid).await.unwrap();
···
827
834
repo.mst.persist().await.unwrap();
828
835
829
836
// Create commit (need a signing key for this test)
830
830
-
let did = Did::new("did:plc:test").unwrap();
837
837
+
let did = Did::new("did:plc:test").unwrap().into_static();
831
838
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
832
839
let (_, commit_cid) = repo.commit(&did, None, &signing_key).await.unwrap();
833
840
···
843
850
let storage = Arc::new(MemoryBlockStore::new());
844
851
let mut repo = create_test_repo(storage.clone()).await;
845
852
846
846
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
847
847
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
853
853
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
854
854
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
848
855
let cid = make_test_cid(1);
849
856
850
857
repo.create_record(&collection, &rkey, cid).await.unwrap();
851
858
repo.mst.persist().await.unwrap();
852
859
853
853
-
let did = Did::new("did:plc:test").unwrap();
860
860
+
let did = Did::new("did:plc:test").unwrap().into_static();
854
861
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
855
862
let (_, commit_cid) = repo.commit(&did, None, &signing_key).await.unwrap();
856
863
···
860
867
861
868
// Verify commit can be deserialized
862
869
let bytes = commit_bytes.unwrap();
863
863
-
let commit = Commit::from_cbor(&bytes).unwrap();
870
870
+
let commit = Commit::<SmolStr>::from_cbor(&bytes).unwrap();
864
871
assert_eq!(commit.did().as_ref(), did.as_ref());
865
872
let root_cid = repo.mst.root().await.unwrap();
866
873
assert_eq!(commit.data(), &root_cid);
···
871
878
let storage = Arc::new(MemoryBlockStore::new());
872
879
let mut repo = create_test_repo(storage.clone()).await;
873
880
874
874
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
875
875
-
let rkey = RecordKey(Rkey::new("test1").unwrap());
881
881
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
882
882
+
let rkey = RecordKey(Rkey::new("test1").unwrap()).into_static();
876
883
let cid1 = make_test_cid(1);
877
884
let cid2 = make_test_cid(2);
878
885
···
899
906
let storage = Arc::new(MemoryBlockStore::new());
900
907
let mut repo = create_test_repo(storage.clone()).await;
901
908
902
902
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
909
909
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
903
910
let mut ticker = Ticker::new();
904
911
905
912
// Add 100 records
906
913
let mut records = Vec::new();
907
914
for i in 0..100 {
908
915
let tid_str = ticker.next(None).into_static();
909
909
-
let rkey = RecordKey(Rkey::from_str(tid_str.as_str()).unwrap());
916
916
+
let rkey: RecordKey<Rkey<SmolStr>> =
917
917
+
RecordKey(Rkey::<SmolStr>::from_str(tid_str.as_str()).unwrap()).into_static();
910
918
let cid = make_test_cid((i % 256) as u8);
911
919
repo.create_record(&collection, &rkey, cid).await.unwrap();
912
920
records.push((rkey, cid));
···
946
954
let storage = Arc::new(MemoryBlockStore::new());
947
955
let mut repo = create_test_repo(storage.clone()).await;
948
956
949
949
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
950
950
-
let rkey = RecordKey(Rkey::new("abc123").unwrap());
957
957
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
958
958
+
let rkey = RecordKey(Rkey::new("abc123").unwrap()).into_static();
951
959
let cid = make_test_cid(1);
952
960
953
961
repo.create_record(&collection, &rkey, cid).await.unwrap();
954
962
repo.mst.persist().await.unwrap();
955
963
956
956
-
let did = Did::new("did:plc:test").unwrap();
964
964
+
let did = Did::new("did:plc:test").unwrap().into_static();
957
965
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
958
966
959
967
// Get public key from signing key
···
968
976
969
977
// Load commit and verify signature
970
978
let commit_bytes = storage.get(&commit_cid).await.unwrap().unwrap();
971
971
-
let commit = Commit::from_cbor(&commit_bytes).unwrap();
979
979
+
let commit = Commit::<SmolStr>::from_cbor(&commit_bytes).unwrap();
972
980
973
981
// Signature verification should succeed
974
982
commit.verify(&pubkey).unwrap();
···
979
987
let storage = Arc::new(MemoryBlockStore::new());
980
988
let mut repo = create_test_repo(storage.clone()).await;
981
989
982
982
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
983
983
-
let did = Did::new("did:plc:test").unwrap();
990
990
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
991
991
+
let did = Did::new("did:plc:test").unwrap().into_static();
984
992
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
985
993
986
994
// Add some records and commit
987
995
let mut records = Vec::new();
988
996
for i in 0..10 {
989
989
-
let rkey = RecordKey(Rkey::from_str(&format!("record{}", i)).unwrap());
997
997
+
let rkey: RecordKey<Rkey<SmolStr>> = RecordKey(
998
998
+
Rkey::<SmolStr>::from_str(&format!("record{}", i))
999
999
+
.unwrap()
1000
1000
+
.into_static(),
1001
1001
+
);
990
1002
let cid = make_test_cid(i as u8);
991
1003
repo.create_record(&collection, &rkey, cid).await.unwrap();
992
1004
records.push((rkey, cid));
···
1021
1033
let storage = Arc::new(MemoryBlockStore::new());
1022
1034
let mut repo = create_test_repo(storage.clone()).await;
1023
1035
1024
1024
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
1025
1025
-
let rkey1 = RecordKey(Rkey::new("test1").unwrap());
1026
1026
-
let rkey2 = RecordKey(Rkey::new("test2").unwrap());
1036
1036
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
1037
1037
+
let rkey1 = RecordKey(Rkey::new("test1").unwrap()).into_static();
1038
1038
+
let rkey2 = RecordKey(Rkey::new("test2").unwrap()).into_static();
1027
1039
1028
1028
-
let did = Did::new("did:plc:test").unwrap();
1040
1040
+
let did = Did::new("did:plc:test").unwrap().into_static();
1029
1041
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
1030
1042
1031
1043
// Create records with actual data
···
1085
1097
let storage = Arc::new(MemoryBlockStore::new());
1086
1098
let mut repo = create_test_repo(storage.clone()).await;
1087
1099
1088
1088
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
1089
1089
-
let rkey1 = RecordKey(Rkey::new("post1").unwrap());
1090
1090
-
let rkey2 = RecordKey(Rkey::new("post2").unwrap());
1100
1100
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
1101
1101
+
let rkey1 = RecordKey(Rkey::new("post1").unwrap()).into_static();
1102
1102
+
let rkey2 = RecordKey(Rkey::new("post2").unwrap()).into_static();
1091
1103
1092
1104
// Create records with actual data
1093
1105
let ops = vec![
···
1104
1116
];
1105
1117
1106
1118
// Format commit
1107
1107
-
let did = Did::new("did:plc:test").unwrap();
1119
1119
+
let did = Did::new("did:plc:test").unwrap().into_static();
1108
1120
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
1109
1121
let (repo_ops, commit_data) = repo
1110
1122
.create_commit(
···
1159
1171
let storage = Arc::new(MemoryBlockStore::new());
1160
1172
let mut repo = create_test_repo(storage.clone()).await;
1161
1173
1162
1162
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
1174
1174
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
1163
1175
1164
1176
// Pre-populate with some records
1165
1165
-
let rkey1 = RecordKey(Rkey::new("existing1").unwrap());
1166
1166
-
let rkey2 = RecordKey(Rkey::new("existing2").unwrap());
1167
1167
-
let rkey3 = RecordKey(Rkey::new("existing3").unwrap());
1177
1177
+
let rkey1 = RecordKey(Rkey::new("existing1").unwrap()).into_static();
1178
1178
+
let rkey2 = RecordKey(Rkey::new("existing2").unwrap()).into_static();
1179
1179
+
let rkey3 = RecordKey(Rkey::new("existing3").unwrap()).into_static();
1168
1180
1169
1169
-
let did = Did::new("did:plc:test").unwrap();
1181
1181
+
let did = Did::new("did:plc:test").unwrap().into_static();
1170
1182
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
1171
1183
1172
1184
let create_ops = vec![
···
1203
1215
repo.apply_commit(commit_data).await.unwrap();
1204
1216
1205
1217
// Batch operation: create new, update existing, delete existing
1206
1206
-
let new_rkey = RecordKey(Rkey::new("new1").unwrap());
1218
1218
+
let new_rkey = RecordKey(Rkey::new("new1").unwrap()).into_static();
1207
1219
let ops = vec![
1208
1220
RecordWriteOp::Create {
1209
1221
collection: collection.clone(),
···
15
15
use jacquard_repo::storage::{BlockStore, MemoryBlockStore};
16
16
use rand::Rng;
17
17
use rand::seq::SliceRandom;
18
18
-
use smol_str::SmolStr;
18
18
+
use smol_str::{SmolStr, ToSmolStr};
19
19
use std::collections::{BTreeMap, HashMap};
20
20
use std::sync::Arc;
21
21
···
50
50
}
51
51
}
52
52
53
53
-
async fn create_test_repo(storage: Arc<MemoryBlockStore>) -> Repository<MemoryBlockStore> {
53
53
+
async fn create_test_repo(storage: Arc<MemoryBlockStore>) -> Repository<SmolStr, MemoryBlockStore> {
54
54
let did = Did::new("did:plc:stresstest").unwrap();
55
55
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
56
56
···
61
61
62
62
/// Track existing records for generating realistic updates/deletes
63
63
struct RecordTracker {
64
64
-
records: HashMap<String, u32>,
64
64
+
records: HashMap<SmolStr, u32>,
65
65
ticker: Ticker,
66
66
}
67
67
···
73
73
}
74
74
}
75
75
76
76
-
fn gen_new_rkey(&mut self) -> String {
77
77
-
self.ticker.next(None).into_static().to_string()
76
76
+
fn gen_new_rkey(&mut self) -> SmolStr {
77
77
+
self.ticker.next(None).into_static().to_smolstr()
78
78
}
79
79
80
80
-
fn pick_random_existing<R: Rng>(&self, rng: &mut R) -> Option<String> {
80
80
+
fn pick_random_existing<R: Rng>(&self, rng: &mut R) -> Option<SmolStr> {
81
81
let keys: Vec<_> = self.records.keys().cloned().collect();
82
82
keys.choose(rng).cloned()
83
83
}
84
84
85
85
-
fn add(&mut self, rkey: String, counter: u32) {
85
85
+
fn add(&mut self, rkey: SmolStr, counter: u32) {
86
86
self.records.insert(rkey, counter);
87
87
}
88
88
···
97
97
98
98
#[derive(Debug, Clone)]
99
99
enum TestOp {
100
100
-
Create { rkey: String, counter: u32 },
101
101
-
Update { rkey: String, counter: u32 },
102
102
-
Delete { rkey: String },
100
100
+
Create { rkey: SmolStr, counter: u32 },
101
101
+
Update { rkey: SmolStr, counter: u32 },
102
102
+
Delete { rkey: SmolStr },
103
103
}
104
104
105
105
fn generate_creates_only<R: Rng>(
···
167
167
ops
168
168
}
169
169
170
170
-
fn test_ops_to_record_writes(ops: Vec<TestOp>, collection: &Nsid) -> Vec<RecordWriteOp<'static>> {
170
170
+
fn test_ops_to_record_writes(
171
171
+
ops: Vec<TestOp>,
172
172
+
collection: &Nsid,
173
173
+
) -> Vec<RecordWriteOp<'_, SmolStr>> {
171
174
let collection_static = collection.clone().into_static();
172
175
ops.into_iter()
173
176
.map(|op| match op {
174
177
TestOp::Create { rkey, counter } => RecordWriteOp::Create {
175
178
collection: collection_static.clone(),
176
176
-
rkey: RecordKey(Rkey::new(&rkey).unwrap()).into_static(),
179
179
+
rkey: RecordKey(Rkey::new(rkey).unwrap()).into_static(),
177
180
record: make_test_record(counter, "Random post"),
178
181
},
179
182
TestOp::Update { rkey, counter } => RecordWriteOp::Update {
180
183
collection: collection_static.clone(),
181
181
-
rkey: RecordKey(Rkey::new(&rkey).unwrap()).into_static(),
184
184
+
rkey: RecordKey(Rkey::new(rkey).unwrap()).into_static(),
182
185
record: make_test_record(counter, "Updated post"),
183
186
prev: None,
184
187
},
185
188
TestOp::Delete { rkey } => RecordWriteOp::Delete {
186
189
collection: collection_static.clone(),
187
187
-
rkey: RecordKey(Rkey::new(&rkey).unwrap()).into_static(),
190
190
+
rkey: RecordKey(Rkey::new(rkey).unwrap()).into_static(),
188
191
prev: None,
189
192
},
190
193
})
···
196
199
let storage = Arc::new(MemoryBlockStore::new());
197
200
let mut repo = create_test_repo(storage.clone()).await;
198
201
199
199
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
200
200
-
let did = Did::new("did:plc:stresstest").unwrap();
202
202
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
203
203
+
let did = Did::new("did:plc:stresstest").unwrap().into_static();
201
204
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
202
205
let pubkey = get_public_key(&signing_key);
203
206
···
308
311
let storage = Arc::new(MemoryBlockStore::new());
309
312
let mut repo = create_test_repo(storage.clone()).await;
310
313
311
311
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
312
312
-
let did = Did::new("did:plc:stresstest").unwrap();
314
314
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
315
315
+
let did = Did::new("did:plc:stresstest").unwrap().into_static();
313
316
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
314
317
let pubkey = get_public_key(&signing_key);
315
318
···
408
411
repo.current_commit_cid()
409
412
);
410
413
411
411
-
let collection = Nsid::new("app.bsky.feed.post").unwrap();
414
414
+
let collection = Nsid::new("app.bsky.feed.post").unwrap().into_static();
412
415
let signing_key = k256::ecdsa::SigningKey::random(&mut rand::rngs::OsRng);
413
416
let pubkey = get_public_key(&signing_key);
414
417
let did = repo.did().clone().into_static();