upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/git/authorization.rs
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-02-23 15:20:59 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-02-23 15:20:59 +0000
commit113928aa84894ea8f65c247d9987527e792b32a9 (patch)
treeec967d6195d9f7ec4f061449596611afe3a0950f /src/git/authorization.rs
parent26f608e5011b9d1ad6036da75b89272835e69695 (diff)
parente0ad39a489b3398f8208713bf728db0cb11475b0 (diff)
Merge master into 3ca0-announcements-purgatory
Diffstat (limited to 'src/git/authorization.rs')
-rw-r--r--src/git/authorization.rs102
1 files changed, 100 insertions, 2 deletions
diff --git a/src/git/authorization.rs b/src/git/authorization.rs
index 69a0751..df780bb 100644
--- a/src/git/authorization.rs
+++ b/src/git/authorization.rs
@@ -609,6 +609,28 @@ pub async fn get_state_authorization_for_specific_owner_repo(
609 owner_pubkey 609 owner_pubkey
610 ); 610 );
611 611
612 // Accept pushes where all refs are already at the desired state (old_oid == new_oid)
613 // This handles race conditions where state events are applied between fetch and push
614 if !pushed_refs.is_empty() {
615 let all_refs_unchanged = pushed_refs
616 .iter()
617 .all(|(old_oid, new_oid, _)| old_oid == new_oid);
618
619 if all_refs_unchanged {
620 debug!(
621 "All pushed refs unchanged (old_oid == new_oid) for {} owned by {}, accepting without purgatory check",
622 identifier, owner_pubkey
623 );
624 return Ok(AuthorizationResult {
625 authorized: true,
626 reason: "Push accepted: all refs already at desired state (no-op)".to_string(),
627 state: None,
628 maintainers: authorized.into_iter().collect(),
629 purgatory_events: vec![],
630 });
631 }
632 }
633
612 // Check purgatory for matching state events 634 // Check purgatory for matching state events
613 // Convert pushed refs to RefUpdate (filter out refs/nostr/* refs) 635 // Convert pushed refs to RefUpdate (filter out refs/nostr/* refs)
614 let pushed_updates: Vec<RefUpdate> = pushed_refs 636 let pushed_updates: Vec<RefUpdate> = pushed_refs
@@ -699,12 +721,88 @@ pub async fn get_state_authorization_for_specific_owner_repo(
699 debug!("Purgatory events found but none from authorized authors"); 721 debug!("Purgatory events found but none from authorized authors");
700 } 722 }
701 } else { 723 } else {
702 debug!("No matching state events found in purgatory"); 724 // Check if there are ANY state events in purgatory for this identifier
725 let all_purgatory_states = purgatory.find_state(identifier);
726
727 if !all_purgatory_states.is_empty() {
728 // There are state events but none match the push - diagnose why
729 debug!(
730 "Found {} state event(s) in purgatory for {} but none match the push",
731 all_purgatory_states.len(),
732 identifier
733 );
734
735 // Count authorized state events and collect diagnostic info
736 let mut authorized_count = 0;
737 let mut diagnostic_reasons = Vec::new();
738
739 // Diagnose why each authorized state event doesn't match
740 for entry in all_purgatory_states.iter() {
741 let author_hex = entry.event.pubkey.to_hex();
742 if authorized.contains(&author_hex) {
743 authorized_count += 1;
744 if let Some(reason) = crate::purgatory::diagnose_state_mismatch(
745 &entry.event,
746 &pushed_updates,
747 &local_refs,
748 ) {
749 debug!(
750 "State event {} from authorized author {} doesn't match push: {}",
751 entry.event.id,
752 entry
753 .event
754 .pubkey
755 .to_bech32()
756 .unwrap_or_else(|_| author_hex.clone()),
757 reason
758 );
759 diagnostic_reasons.push(reason);
760 }
761 }
762 }
763
764 // Create concise WARN message summarizing the rejection
765 let summary = if authorized_count > 0 {
766 let reason_summary = if !diagnostic_reasons.is_empty() {
767 // Take the first diagnostic reason as representative
768 format!(" ({})", diagnostic_reasons[0])
769 } else {
770 String::new()
771 };
772 format!(
773 "{} state event{} in purgatory from authorized publisher{} but doesn't match push{}",
774 authorized_count,
775 if authorized_count == 1 { "" } else { "s" },
776 if authorized_count == 1 { "" } else { "s" },
777 reason_summary
778 )
779 } else {
780 format!(
781 "{} state event{} in purgatory but none from authorized publishers",
782 all_purgatory_states.len(),
783 if all_purgatory_states.len() == 1 {
784 ""
785 } else {
786 "s"
787 }
788 )
789 };
790
791 warn!("Push rejected for {}: {}", identifier, summary);
792 return Ok(AuthorizationResult::denied(summary));
793 } else {
794 debug!("No state events found in purgatory for {}", identifier);
795 warn!(
796 "Push rejected for {}: No state events in purgatory",
797 identifier
798 );
799 return Ok(AuthorizationResult::denied("No state events in purgatory"));
800 }
703 } 801 }
704 802
705 // No matching state found in purgatory 803 // No matching state found in purgatory
706 Ok(AuthorizationResult::denied( 804 Ok(AuthorizationResult::denied(
707 "No state event found in purgatory from authorized publishers", 805 "No matching state event found in purgatory from authorized publishers",
708 )) 806 ))
709} 807}
710 808