+1
-1
crates/atproto-client/src/client.rs
+1
-1
crates/atproto-client/src/client.rs
+2
-2
crates/atproto-client/src/com_atproto_identity.rs
+2
-2
crates/atproto-client/src/com_atproto_identity.rs
+4
-1
crates/atproto-client/src/com_atproto_repo.rs
+4
-1
crates/atproto-client/src/com_atproto_repo.rs
+1
-1
crates/atproto-client/src/lib.rs
+1
-1
crates/atproto-client/src/lib.rs
+149
-48
crates/atproto-jetstream/src/consumer.rs
+149
-48
crates/atproto-jetstream/src/consumer.rs
···
456
#[test]
457
fn test_parse_account_event() {
458
let json_str = r#"{"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","time_us":1757517640675638,"kind":"account","account":{"active":false,"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","seq":13206502767,"status":"takendown","time":"2025-09-10T15:20:40.439Z"}}"#;
459
-
460
let event = serde_json::from_str::<JetstreamEvent>(json_str)
461
.expect("Failed to parse account event JSON");
462
-
463
match event {
464
-
JetstreamEvent::Account { did, time_us, kind, account: identity } => {
465
assert_eq!(did, "did:plc:yn72uqr4ihkjfbz7us7buqsq");
466
assert_eq!(time_us, 1757517640675638);
467
assert_eq!(kind, "account");
468
-
469
// Verify the account data structure
470
assert!(identity.is_object());
471
let account_obj = identity.as_object().unwrap();
472
-
assert_eq!(account_obj.get("active").unwrap(), &serde_json::json!(false));
473
-
assert_eq!(account_obj.get("did").unwrap(), &serde_json::json!("did:plc:yn72uqr4ihkjfbz7us7buqsq"));
474
-
assert_eq!(account_obj.get("seq").unwrap(), &serde_json::json!(13206502767i64));
475
-
assert_eq!(account_obj.get("status").unwrap(), &serde_json::json!("takendown"));
476
-
assert_eq!(account_obj.get("time").unwrap(), &serde_json::json!("2025-09-10T15:20:40.439Z"));
477
}
478
_ => panic!("Expected JetstreamEvent::Account variant, got {:?}", event),
479
}
···
482
#[test]
483
fn test_parse_identity_event() {
484
let json_str = r#"{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","time_us":1757517628039893,"kind":"identity","identity":{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","handle":"nhieothv.bsky.social","seq":13206497272,"time":"2025-09-10T15:20:27.610Z"}}"#;
485
-
486
let event = serde_json::from_str::<JetstreamEvent>(json_str)
487
.expect("Failed to parse identity event JSON");
488
-
489
match event {
490
-
JetstreamEvent::Identity { did, time_us, kind, identity } => {
491
assert_eq!(did, "did:plc:mbuadp4xzlbmc2ncqp3pmtox");
492
assert_eq!(time_us, 1757517628039893);
493
assert_eq!(kind, "identity");
494
-
495
// Verify the identity data structure
496
assert!(identity.is_object());
497
let identity_obj = identity.as_object().unwrap();
498
-
assert_eq!(identity_obj.get("did").unwrap(), &serde_json::json!("did:plc:mbuadp4xzlbmc2ncqp3pmtox"));
499
-
assert_eq!(identity_obj.get("handle").unwrap(), &serde_json::json!("nhieothv.bsky.social"));
500
-
assert_eq!(identity_obj.get("seq").unwrap(), &serde_json::json!(13206497272i64));
501
-
assert_eq!(identity_obj.get("time").unwrap(), &serde_json::json!("2025-09-10T15:20:27.610Z"));
502
}
503
_ => panic!("Expected JetstreamEvent::Identity variant, got {:?}", event),
504
}
···
507
#[test]
508
fn test_parse_delete_event() {
509
let json_str = r#"{"did":"did:plc:5ozthefrqdo5kqnxzfgthhpp","time_us":1757519323847323,"kind":"commit","commit":{"rev":"3lyileto4q52k","operation":"delete","collection":"app.bsky.graph.follow","rkey":"3lxqxntaew32z"}}"#;
510
-
511
let event = serde_json::from_str::<JetstreamEvent>(json_str)
512
.expect("Failed to parse delete event JSON");
513
-
514
match event {
515
-
JetstreamEvent::Delete { did, time_us, kind, commit } => {
516
assert_eq!(did, "did:plc:5ozthefrqdo5kqnxzfgthhpp");
517
assert_eq!(time_us, 1757519323847323);
518
assert_eq!(kind, "commit");
519
-
520
// Verify the delete operation details
521
assert_eq!(commit.rev, "3lyileto4q52k");
522
assert_eq!(commit.operation, "delete");
···
530
#[test]
531
fn test_parse_commit_event() {
532
let json_str = r#"{"did":"did:plc:suq5ijgyqmsawwf5tskf654x","time_us":1757519323848962,"kind":"commit","commit":{"rev":"3lyiletdopl2c","operation":"create","collection":"app.bsky.feed.like","rkey":"3lyiletddxt2c","record":{"$type":"app.bsky.feed.like","createdAt":"2025-09-10T15:47:13.086Z","subject":{"cid":"bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4","uri":"at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o"}},"cid":"bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy"}}"#;
533
-
534
let event = serde_json::from_str::<JetstreamEvent>(json_str)
535
.expect("Failed to parse commit event JSON");
536
-
537
match event {
538
-
JetstreamEvent::Commit { did, time_us, kind, commit } => {
539
assert_eq!(did, "did:plc:suq5ijgyqmsawwf5tskf654x");
540
assert_eq!(time_us, 1757519323848962);
541
assert_eq!(kind, "commit");
542
-
543
// Verify the commit operation details
544
assert_eq!(commit.rev, "3lyiletdopl2c");
545
assert_eq!(commit.operation, "create");
546
assert_eq!(commit.collection, "app.bsky.feed.like");
547
assert_eq!(commit.rkey, "3lyiletddxt2c");
548
-
assert_eq!(commit.cid, "bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy");
549
-
550
// Verify the record data structure
551
assert!(commit.record.is_object());
552
let record_obj = commit.record.as_object().unwrap();
553
-
assert_eq!(record_obj.get("$type").unwrap(), &serde_json::json!("app.bsky.feed.like"));
554
-
assert_eq!(record_obj.get("createdAt").unwrap(), &serde_json::json!("2025-09-10T15:47:13.086Z"));
555
-
556
// Verify the subject within the record
557
let subject = record_obj.get("subject").unwrap().as_object().unwrap();
558
-
assert_eq!(subject.get("cid").unwrap(), &serde_json::json!("bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4"));
559
-
assert_eq!(subject.get("uri").unwrap(), &serde_json::json!("at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o"));
560
}
561
_ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event),
562
}
···
565
#[test]
566
fn test_parse_commit_update_event() {
567
let json_str = r#"{"did":"did:plc:mek6cpladv2xrlu2zdykoxgz","time_us":1757519523286358,"kind":"commit","commit":{"rev":"3lyilmalk762z","operation":"update","collection":"app.bsky.actor.profile","rkey":"self","record":{"$type":"app.bsky.actor.profile","avatar":{"$type":"blob","ref":{"$link":"bafkreibmn7xi5iwugioov463wux62dg4m4w6qqrbsnileaobrzgxdwbsqy"},"mimeType":"image/jpeg","size":289838},"banner":{"$type":"blob","ref":{"$link":"bafkreicjgdlfs6fyyddjklfzrf6w2boychodkdebtjaiwavhcffjtuavsi"},"mimeType":"image/jpeg","size":676693},"description":"ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva","displayName":"la mucura mística","pinnedPost":{"cid":"bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq","uri":"at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j"}},"cid":"bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4"}}"#;
568
-
569
let event = serde_json::from_str::<JetstreamEvent>(json_str)
570
.expect("Failed to parse commit update event JSON");
571
-
572
match event {
573
-
JetstreamEvent::Commit { did, time_us, kind, commit } => {
574
assert_eq!(did, "did:plc:mek6cpladv2xrlu2zdykoxgz");
575
assert_eq!(time_us, 1757519523286358);
576
assert_eq!(kind, "commit");
577
-
578
// Verify the commit operation details
579
assert_eq!(commit.rev, "3lyilmalk762z");
580
assert_eq!(commit.operation, "update");
581
assert_eq!(commit.collection, "app.bsky.actor.profile");
582
assert_eq!(commit.rkey, "self");
583
-
assert_eq!(commit.cid, "bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4");
584
-
585
// Verify the record data structure
586
assert!(commit.record.is_object());
587
let record_obj = commit.record.as_object().unwrap();
588
-
assert_eq!(record_obj.get("$type").unwrap(), &serde_json::json!("app.bsky.actor.profile"));
589
-
assert_eq!(record_obj.get("description").unwrap(), &serde_json::json!("ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva"));
590
-
assert_eq!(record_obj.get("displayName").unwrap(), &serde_json::json!("la mucura mística"));
591
-
592
// Verify avatar blob
593
let avatar = record_obj.get("avatar").unwrap().as_object().unwrap();
594
assert_eq!(avatar.get("$type").unwrap(), &serde_json::json!("blob"));
595
-
assert_eq!(avatar.get("mimeType").unwrap(), &serde_json::json!("image/jpeg"));
596
assert_eq!(avatar.get("size").unwrap(), &serde_json::json!(289838));
597
-
598
// Verify banner blob
599
let banner = record_obj.get("banner").unwrap().as_object().unwrap();
600
assert_eq!(banner.get("$type").unwrap(), &serde_json::json!("blob"));
601
-
assert_eq!(banner.get("mimeType").unwrap(), &serde_json::json!("image/jpeg"));
602
assert_eq!(banner.get("size").unwrap(), &serde_json::json!(676693));
603
-
604
// Verify pinned post
605
let pinned_post = record_obj.get("pinnedPost").unwrap().as_object().unwrap();
606
-
assert_eq!(pinned_post.get("cid").unwrap(), &serde_json::json!("bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq"));
607
-
assert_eq!(pinned_post.get("uri").unwrap(), &serde_json::json!("at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j"));
608
}
609
_ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event),
610
}
···
456
#[test]
457
fn test_parse_account_event() {
458
let json_str = r#"{"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","time_us":1757517640675638,"kind":"account","account":{"active":false,"did":"did:plc:yn72uqr4ihkjfbz7us7buqsq","seq":13206502767,"status":"takendown","time":"2025-09-10T15:20:40.439Z"}}"#;
459
+
460
let event = serde_json::from_str::<JetstreamEvent>(json_str)
461
.expect("Failed to parse account event JSON");
462
+
463
match event {
464
+
JetstreamEvent::Account {
465
+
did,
466
+
time_us,
467
+
kind,
468
+
account: identity,
469
+
} => {
470
assert_eq!(did, "did:plc:yn72uqr4ihkjfbz7us7buqsq");
471
assert_eq!(time_us, 1757517640675638);
472
assert_eq!(kind, "account");
473
+
474
// Verify the account data structure
475
assert!(identity.is_object());
476
let account_obj = identity.as_object().unwrap();
477
+
assert_eq!(
478
+
account_obj.get("active").unwrap(),
479
+
&serde_json::json!(false)
480
+
);
481
+
assert_eq!(
482
+
account_obj.get("did").unwrap(),
483
+
&serde_json::json!("did:plc:yn72uqr4ihkjfbz7us7buqsq")
484
+
);
485
+
assert_eq!(
486
+
account_obj.get("seq").unwrap(),
487
+
&serde_json::json!(13206502767i64)
488
+
);
489
+
assert_eq!(
490
+
account_obj.get("status").unwrap(),
491
+
&serde_json::json!("takendown")
492
+
);
493
+
assert_eq!(
494
+
account_obj.get("time").unwrap(),
495
+
&serde_json::json!("2025-09-10T15:20:40.439Z")
496
+
);
497
}
498
_ => panic!("Expected JetstreamEvent::Account variant, got {:?}", event),
499
}
···
502
#[test]
503
fn test_parse_identity_event() {
504
let json_str = r#"{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","time_us":1757517628039893,"kind":"identity","identity":{"did":"did:plc:mbuadp4xzlbmc2ncqp3pmtox","handle":"nhieothv.bsky.social","seq":13206497272,"time":"2025-09-10T15:20:27.610Z"}}"#;
505
+
506
let event = serde_json::from_str::<JetstreamEvent>(json_str)
507
.expect("Failed to parse identity event JSON");
508
+
509
match event {
510
+
JetstreamEvent::Identity {
511
+
did,
512
+
time_us,
513
+
kind,
514
+
identity,
515
+
} => {
516
assert_eq!(did, "did:plc:mbuadp4xzlbmc2ncqp3pmtox");
517
assert_eq!(time_us, 1757517628039893);
518
assert_eq!(kind, "identity");
519
+
520
// Verify the identity data structure
521
assert!(identity.is_object());
522
let identity_obj = identity.as_object().unwrap();
523
+
assert_eq!(
524
+
identity_obj.get("did").unwrap(),
525
+
&serde_json::json!("did:plc:mbuadp4xzlbmc2ncqp3pmtox")
526
+
);
527
+
assert_eq!(
528
+
identity_obj.get("handle").unwrap(),
529
+
&serde_json::json!("nhieothv.bsky.social")
530
+
);
531
+
assert_eq!(
532
+
identity_obj.get("seq").unwrap(),
533
+
&serde_json::json!(13206497272i64)
534
+
);
535
+
assert_eq!(
536
+
identity_obj.get("time").unwrap(),
537
+
&serde_json::json!("2025-09-10T15:20:27.610Z")
538
+
);
539
}
540
_ => panic!("Expected JetstreamEvent::Identity variant, got {:?}", event),
541
}
···
544
#[test]
545
fn test_parse_delete_event() {
546
let json_str = r#"{"did":"did:plc:5ozthefrqdo5kqnxzfgthhpp","time_us":1757519323847323,"kind":"commit","commit":{"rev":"3lyileto4q52k","operation":"delete","collection":"app.bsky.graph.follow","rkey":"3lxqxntaew32z"}}"#;
547
+
548
let event = serde_json::from_str::<JetstreamEvent>(json_str)
549
.expect("Failed to parse delete event JSON");
550
+
551
match event {
552
+
JetstreamEvent::Delete {
553
+
did,
554
+
time_us,
555
+
kind,
556
+
commit,
557
+
} => {
558
assert_eq!(did, "did:plc:5ozthefrqdo5kqnxzfgthhpp");
559
assert_eq!(time_us, 1757519323847323);
560
assert_eq!(kind, "commit");
561
+
562
// Verify the delete operation details
563
assert_eq!(commit.rev, "3lyileto4q52k");
564
assert_eq!(commit.operation, "delete");
···
572
#[test]
573
fn test_parse_commit_event() {
574
let json_str = r#"{"did":"did:plc:suq5ijgyqmsawwf5tskf654x","time_us":1757519323848962,"kind":"commit","commit":{"rev":"3lyiletdopl2c","operation":"create","collection":"app.bsky.feed.like","rkey":"3lyiletddxt2c","record":{"$type":"app.bsky.feed.like","createdAt":"2025-09-10T15:47:13.086Z","subject":{"cid":"bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4","uri":"at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o"}},"cid":"bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy"}}"#;
575
+
576
let event = serde_json::from_str::<JetstreamEvent>(json_str)
577
.expect("Failed to parse commit event JSON");
578
+
579
match event {
580
+
JetstreamEvent::Commit {
581
+
did,
582
+
time_us,
583
+
kind,
584
+
commit,
585
+
} => {
586
assert_eq!(did, "did:plc:suq5ijgyqmsawwf5tskf654x");
587
assert_eq!(time_us, 1757519323848962);
588
assert_eq!(kind, "commit");
589
+
590
// Verify the commit operation details
591
assert_eq!(commit.rev, "3lyiletdopl2c");
592
assert_eq!(commit.operation, "create");
593
assert_eq!(commit.collection, "app.bsky.feed.like");
594
assert_eq!(commit.rkey, "3lyiletddxt2c");
595
+
assert_eq!(
596
+
commit.cid,
597
+
"bafyreigroo6vhxt62ufcndhaxzas6btq4jmniuz4egszbwuqgiyisqwqoy"
598
+
);
599
+
600
// Verify the record data structure
601
assert!(commit.record.is_object());
602
let record_obj = commit.record.as_object().unwrap();
603
+
assert_eq!(
604
+
record_obj.get("$type").unwrap(),
605
+
&serde_json::json!("app.bsky.feed.like")
606
+
);
607
+
assert_eq!(
608
+
record_obj.get("createdAt").unwrap(),
609
+
&serde_json::json!("2025-09-10T15:47:13.086Z")
610
+
);
611
+
612
// Verify the subject within the record
613
let subject = record_obj.get("subject").unwrap().as_object().unwrap();
614
+
assert_eq!(
615
+
subject.get("cid").unwrap(),
616
+
&serde_json::json!(
617
+
"bafyreib2pygab7z5l7nkqf6bchcvgt4jwsqiaenpf3sr65lugum2uvzzf4"
618
+
)
619
+
);
620
+
assert_eq!(
621
+
subject.get("uri").unwrap(),
622
+
&serde_json::json!(
623
+
"at://did:plc:yw65rktdby2chplqdytqzcao/app.bsky.feed.post/3lyildyjxgs2o"
624
+
)
625
+
);
626
}
627
_ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event),
628
}
···
631
#[test]
632
fn test_parse_commit_update_event() {
633
let json_str = r#"{"did":"did:plc:mek6cpladv2xrlu2zdykoxgz","time_us":1757519523286358,"kind":"commit","commit":{"rev":"3lyilmalk762z","operation":"update","collection":"app.bsky.actor.profile","rkey":"self","record":{"$type":"app.bsky.actor.profile","avatar":{"$type":"blob","ref":{"$link":"bafkreibmn7xi5iwugioov463wux62dg4m4w6qqrbsnileaobrzgxdwbsqy"},"mimeType":"image/jpeg","size":289838},"banner":{"$type":"blob","ref":{"$link":"bafkreicjgdlfs6fyyddjklfzrf6w2boychodkdebtjaiwavhcffjtuavsi"},"mimeType":"image/jpeg","size":676693},"description":"ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva","displayName":"la mucura mística","pinnedPost":{"cid":"bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq","uri":"at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j"}},"cid":"bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4"}}"#;
634
+
635
let event = serde_json::from_str::<JetstreamEvent>(json_str)
636
.expect("Failed to parse commit update event JSON");
637
+
638
match event {
639
+
JetstreamEvent::Commit {
640
+
did,
641
+
time_us,
642
+
kind,
643
+
commit,
644
+
} => {
645
assert_eq!(did, "did:plc:mek6cpladv2xrlu2zdykoxgz");
646
assert_eq!(time_us, 1757519523286358);
647
assert_eq!(kind, "commit");
648
+
649
// Verify the commit operation details
650
assert_eq!(commit.rev, "3lyilmalk762z");
651
assert_eq!(commit.operation, "update");
652
assert_eq!(commit.collection, "app.bsky.actor.profile");
653
assert_eq!(commit.rkey, "self");
654
+
assert_eq!(
655
+
commit.cid,
656
+
"bafyreifpmgw3podvvm4raq6zewn6jhoa73t7mlgf3f7hty2adb6f2ga7j4"
657
+
);
658
+
659
// Verify the record data structure
660
assert!(commit.record.is_object());
661
let record_obj = commit.record.as_object().unwrap();
662
+
assert_eq!(
663
+
record_obj.get("$type").unwrap(),
664
+
&serde_json::json!("app.bsky.actor.profile")
665
+
);
666
+
assert_eq!(
667
+
record_obj.get("description").unwrap(),
668
+
&serde_json::json!(
669
+
"ela/dela | parte da fauna fantástica do céu azul | praticamente inofensiva"
670
+
)
671
+
);
672
+
assert_eq!(
673
+
record_obj.get("displayName").unwrap(),
674
+
&serde_json::json!("la mucura mística")
675
+
);
676
+
677
// Verify avatar blob
678
let avatar = record_obj.get("avatar").unwrap().as_object().unwrap();
679
assert_eq!(avatar.get("$type").unwrap(), &serde_json::json!("blob"));
680
+
assert_eq!(
681
+
avatar.get("mimeType").unwrap(),
682
+
&serde_json::json!("image/jpeg")
683
+
);
684
assert_eq!(avatar.get("size").unwrap(), &serde_json::json!(289838));
685
+
686
// Verify banner blob
687
let banner = record_obj.get("banner").unwrap().as_object().unwrap();
688
assert_eq!(banner.get("$type").unwrap(), &serde_json::json!("blob"));
689
+
assert_eq!(
690
+
banner.get("mimeType").unwrap(),
691
+
&serde_json::json!("image/jpeg")
692
+
);
693
assert_eq!(banner.get("size").unwrap(), &serde_json::json!(676693));
694
+
695
// Verify pinned post
696
let pinned_post = record_obj.get("pinnedPost").unwrap().as_object().unwrap();
697
+
assert_eq!(
698
+
pinned_post.get("cid").unwrap(),
699
+
&serde_json::json!(
700
+
"bafyreihn2t4efvipbcignd6rlybmoecb7hx4jgntsojhpibjzxno3zhbuq"
701
+
)
702
+
);
703
+
assert_eq!(
704
+
pinned_post.get("uri").unwrap(),
705
+
&serde_json::json!(
706
+
"at://did:plc:mek6cpladv2xrlu2zdykoxgz/app.bsky.feed.post/3lxarfbd4ts2j"
707
+
)
708
+
);
709
}
710
_ => panic!("Expected JetstreamEvent::Commit variant, got {:?}", event),
711
}
+11
-4
crates/atproto-oauth-aip/src/workflow.rs
+11
-4
crates/atproto-oauth-aip/src/workflow.rs
···
112
//! and protocol violations.
113
114
use anyhow::Result;
115
-
use atproto_oauth::workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse};
116
use serde::Deserialize;
117
118
use crate::errors::OAuthWorkflowError;
···
176
#[cfg_attr(feature = "zeroize", zeroize(skip))]
177
pub pds_endpoint: String,
178
179
/// The DPoP (Demonstration of Proof-of-Possession) key in JWK format.
180
#[cfg_attr(feature = "zeroize", zeroize(skip))]
181
-
pub dpop_key: String,
182
183
/// Unix timestamp indicating when this session expires.
184
#[cfg_attr(feature = "zeroize", zeroize(skip))]
···
189
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
190
#[serde(untagged)]
191
enum WrappedATProtocolSession {
192
-
ATProtocolSession(ATProtocolSession),
193
194
#[cfg_attr(feature = "zeroize", zeroize(skip))]
195
Error {
···
428
.map_err(OAuthWorkflowError::SessionResponseParseFailed)?;
429
430
match response {
431
-
WrappedATProtocolSession::ATProtocolSession(ref value) => Ok(value.clone()),
432
WrappedATProtocolSession::Error {
433
ref error,
434
ref error_description,
···
112
//! and protocol violations.
113
114
use anyhow::Result;
115
+
use atproto_oauth::{
116
+
jwk::WrappedJsonWebKey,
117
+
workflow::{OAuthRequest, OAuthRequestState, ParResponse, TokenResponse},
118
+
};
119
use serde::Deserialize;
120
121
use crate::errors::OAuthWorkflowError;
···
179
#[cfg_attr(feature = "zeroize", zeroize(skip))]
180
pub pds_endpoint: String,
181
182
+
/// The DPoP (Demonstration of Proof-of-Possession) key in string serialized format.
183
+
#[cfg_attr(feature = "zeroize", zeroize(skip))]
184
+
pub dpop_key: Option<String>,
185
+
186
/// The DPoP (Demonstration of Proof-of-Possession) key in JWK format.
187
#[cfg_attr(feature = "zeroize", zeroize(skip))]
188
+
pub dpop_jwk: Option<WrappedJsonWebKey>,
189
190
/// Unix timestamp indicating when this session expires.
191
#[cfg_attr(feature = "zeroize", zeroize(skip))]
···
196
#[cfg_attr(feature = "zeroize", derive(Zeroize, ZeroizeOnDrop))]
197
#[serde(untagged)]
198
enum WrappedATProtocolSession {
199
+
ATProtocolSession(Box<ATProtocolSession>),
200
201
#[cfg_attr(feature = "zeroize", zeroize(skip))]
202
Error {
···
435
.map_err(OAuthWorkflowError::SessionResponseParseFailed)?;
436
437
match response {
438
+
WrappedATProtocolSession::ATProtocolSession(ref value) => Ok(*value.clone()),
439
WrappedATProtocolSession::Error {
440
ref error,
441
ref error_description,
+2
-2
crates/atproto-oauth/src/lib.rs
+2
-2
crates/atproto-oauth/src/lib.rs
···
20
pub mod pkce;
21
/// OAuth resource and authorization server management.
22
pub mod resources;
23
/// OAuth request storage abstraction for CRUD operations.
24
pub mod storage;
25
/// LRU-based implementation of OAuth request storage (requires `lru` feature).
···
27
pub mod storage_lru;
28
/// OAuth workflow implementation for AT Protocol authorization flows.
29
pub mod workflow;
30
-
/// OAuth 2.0 scope definitions and parsing for AT Protocol.
31
-
pub mod scopes;
···
20
pub mod pkce;
21
/// OAuth resource and authorization server management.
22
pub mod resources;
23
+
/// OAuth 2.0 scope definitions and parsing for AT Protocol.
24
+
pub mod scopes;
25
/// OAuth request storage abstraction for CRUD operations.
26
pub mod storage;
27
/// LRU-based implementation of OAuth request storage (requires `lru` feature).
···
29
pub mod storage_lru;
30
/// OAuth workflow implementation for AT Protocol authorization flows.
31
pub mod workflow;
+24
-26
crates/atproto-oauth/src/scopes.rs
+24
-26
crates/atproto-oauth/src/scopes.rs
···
182
}
183
184
let mut scopes = Vec::new();
185
-
for scope_str in s.trim().split_whitespace() {
186
scopes.push(Self::parse(scope_str)?);
187
}
188
···
318
let mut suffix = None;
319
320
for prefix in &prefixes {
321
-
if s.starts_with(prefix) {
322
-
let remainder = &s[prefix.len()..];
323
-
if remainder.is_empty() || remainder.starts_with(':') || remainder.starts_with('?')
324
-
{
325
-
found_prefix = Some(*prefix);
326
-
if remainder.starts_with(':') {
327
-
suffix = Some(&remainder[1..]);
328
-
} else if remainder.starts_with('?') {
329
-
suffix = Some(remainder);
330
-
} else {
331
-
suffix = None;
332
-
}
333
-
break;
334
}
335
}
336
}
337
···
744
(_, Scope::Email) => false,
745
(Scope::Account(a), Scope::Account(b)) => {
746
a.resource == b.resource
747
-
&& match (a.action, b.action) {
748
-
(AccountAction::Manage, _) => true,
749
-
(AccountAction::Read, AccountAction::Read) => true,
750
-
_ => false,
751
-
}
752
}
753
-
(Scope::Identity(a), Scope::Identity(b)) => match (a, b) {
754
-
(IdentityScope::All, _) => true,
755
-
(IdentityScope::Handle, IdentityScope::Handle) => true,
756
-
_ => false,
757
-
},
758
(Scope::Blob(a), Scope::Blob(b)) => {
759
for b_pattern in &b.accept {
760
let mut granted = false;
···
833
fn from_str(s: &str) -> Result<Self, Self::Err> {
834
if s == "*/*" {
835
Ok(MimePattern::All)
836
-
} else if s.ends_with("/*") {
837
-
Ok(MimePattern::TypeWildcard(s[..s.len() - 2].to_string()))
838
} else if s.contains('/') {
839
Ok(MimePattern::Exact(s.to_string()))
840
} else {
···
182
}
183
184
let mut scopes = Vec::new();
185
+
for scope_str in s.split_whitespace() {
186
scopes.push(Self::parse(scope_str)?);
187
}
188
···
318
let mut suffix = None;
319
320
for prefix in &prefixes {
321
+
if let Some(remainder) = s.strip_prefix(prefix)
322
+
&& (remainder.is_empty()
323
+
|| remainder.starts_with(':')
324
+
|| remainder.starts_with('?'))
325
+
{
326
+
found_prefix = Some(*prefix);
327
+
if let Some(stripped) = remainder.strip_prefix(':') {
328
+
suffix = Some(stripped);
329
+
} else if remainder.starts_with('?') {
330
+
suffix = Some(remainder);
331
+
} else {
332
+
suffix = None;
333
}
334
+
break;
335
}
336
}
337
···
744
(_, Scope::Email) => false,
745
(Scope::Account(a), Scope::Account(b)) => {
746
a.resource == b.resource
747
+
&& matches!(
748
+
(a.action, b.action),
749
+
(AccountAction::Manage, _) | (AccountAction::Read, AccountAction::Read)
750
+
)
751
}
752
+
(Scope::Identity(a), Scope::Identity(b)) => matches!(
753
+
(a, b),
754
+
(IdentityScope::All, _) | (IdentityScope::Handle, IdentityScope::Handle)
755
+
),
756
(Scope::Blob(a), Scope::Blob(b)) => {
757
for b_pattern in &b.accept {
758
let mut granted = false;
···
831
fn from_str(s: &str) -> Result<Self, Self::Err> {
832
if s == "*/*" {
833
Ok(MimePattern::All)
834
+
} else if let Some(stripped) = s.strip_suffix("/*") {
835
+
Ok(MimePattern::TypeWildcard(stripped.to_string()))
836
} else if s.contains('/') {
837
Ok(MimePattern::Exact(s.to_string()))
838
} else {