upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/nostr
diff options
context:
space:
mode:
Diffstat (limited to 'src/nostr')
-rw-r--r--src/nostr/builder.rs72
-rw-r--r--src/nostr/policy/state.rs40
2 files changed, 112 insertions, 0 deletions
diff --git a/src/nostr/builder.rs b/src/nostr/builder.rs
index 939ccef..acaac71 100644
--- a/src/nostr/builder.rs
+++ b/src/nostr/builder.rs
@@ -102,6 +102,11 @@ impl Nip34WritePolicy {
102 } 102 }
103 103
104 tracing::debug!("Accepted repository announcement: {}", event_id_str); 104 tracing::debug!("Accepted repository announcement: {}", event_id_str);
105
106 // Check purgatory for state events that might now be authorized
107 self.check_purgatory_state_events_for_identifier(&announcement.identifier)
108 .await;
109
105 WritePolicyResult::Accept 110 WritePolicyResult::Accept
106 } 111 }
107 Err(e) => { 112 Err(e) => {
@@ -125,6 +130,11 @@ impl Nip34WritePolicy {
125 announcement.identifier 130 announcement.identifier
126 ); 131 );
127 // Don't create bare repository for external announcements 132 // Don't create bare repository for external announcements
133
134 // Check purgatory for state events that might now be authorized
135 self.check_purgatory_state_events_for_identifier(&announcement.identifier)
136 .await;
137
128 WritePolicyResult::Accept 138 WritePolicyResult::Accept
129 } 139 }
130 Err(e) => { 140 Err(e) => {
@@ -304,6 +314,68 @@ impl Nip34WritePolicy {
304 } 314 }
305 } 315 }
306 316
317 /// Check purgatory for state events that might now be authorized by a new announcement
318 ///
319 /// When an announcement is accepted, state events in purgatory that were previously
320 /// rejected due to missing announcements might now be authorized. This method:
321 /// 1. Finds all state events in purgatory for the identifier
322 /// 2. Re-evaluates authorization for each event
323 /// 3. Processes authorized events (releases from purgatory)
324 /// 4. Keeps unauthorized events in purgatory (will expire naturally)
325 async fn check_purgatory_state_events_for_identifier(&self, identifier: &str) {
326 let state_events = self.ctx.purgatory.find_state(identifier);
327
328 if state_events.is_empty() {
329 return;
330 }
331
332 tracing::debug!(
333 identifier = %identifier,
334 count = state_events.len(),
335 "Checking purgatory state events after announcement acceptance"
336 );
337
338 for entry in state_events {
339 // Re-evaluate authorization with the new announcement
340 match self.state_policy.process_state_event(&entry.event, false).await {
341 Ok(WritePolicyResult::Accept) => {
342 tracing::info!(
343 event_id = %entry.event.id,
344 identifier = %identifier,
345 "State event in purgatory now authorized, will be processed"
346 );
347 // Event will be automatically removed from purgatory by process_state_event
348 // and broadcast to subscribers
349 }
350 Ok(WritePolicyResult::Reject { message, .. }) => {
351 if message.contains("not authorized") {
352 tracing::debug!(
353 event_id = %entry.event.id,
354 identifier = %identifier,
355 "State event in purgatory still not authorized, keeping in purgatory"
356 );
357 // Keep in purgatory - will expire naturally after 30 minutes
358 } else {
359 tracing::debug!(
360 event_id = %entry.event.id,
361 identifier = %identifier,
362 reason = %message,
363 "State event in purgatory rejected for other reason"
364 );
365 }
366 }
367 Err(e) => {
368 tracing::warn!(
369 event_id = %entry.event.id,
370 identifier = %identifier,
371 error = %e,
372 "Error re-evaluating state event in purgatory"
373 );
374 }
375 }
376 }
377 }
378
307 /// Handle events that must reference accepted repositories or events 379 /// Handle events that must reference accepted repositories or events
308 async fn handle_related_event(&self, event: &Event, event_type: &str) -> WritePolicyResult { 380 async fn handle_related_event(&self, event: &Event, event_type: &str) -> WritePolicyResult {
309 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex()); 381 let event_id_str = event.id.to_bech32().unwrap_or_else(|_| event.id.to_hex());
diff --git a/src/nostr/policy/state.rs b/src/nostr/policy/state.rs
index acb76a3..d26b5ec 100644
--- a/src/nostr/policy/state.rs
+++ b/src/nostr/policy/state.rs
@@ -78,6 +78,46 @@ impl StatePolicy {
78 // Get all repositories and state events from db with identifier 78 // Get all repositories and state events from db with identifier
79 let db_repo_data = fetch_repository_data(&self.ctx.database, &state.identifier).await?; 79 let db_repo_data = fetch_repository_data(&self.ctx.database, &state.identifier).await?;
80 80
81 // CRITICAL: Check if author is authorized via maintainer set
82 // State events MUST be rejected if author is not in maintainer set of any accepted announcement
83 if db_repo_data.announcements.is_empty() {
84 tracing::warn!(
85 event_id = %event.id,
86 identifier = %state.identifier,
87 author = %event.pubkey.to_hex(),
88 "Rejecting state event: no announcement exists for this repository"
89 );
90 return Ok(WritePolicyResult::Reject {
91 status: false,
92 message: "invalid: no announcement exists for this repository".into(),
93 });
94 }
95
96 let authorized_owners =
97 crate::git::authorization::pubkey_authorised_for_repo_owners(&event.pubkey, &db_repo_data);
98
99 if authorized_owners.is_empty() {
100 tracing::warn!(
101 event_id = %event.id,
102 identifier = %state.identifier,
103 author = %event.pubkey.to_hex(),
104 announcements_count = db_repo_data.announcements.len(),
105 "Rejecting state event: author not in maintainer set of any announcement"
106 );
107 return Ok(WritePolicyResult::Reject {
108 status: false,
109 message: "invalid: author not authorized for this repository".into(),
110 });
111 }
112
113 tracing::debug!(
114 event_id = %event.id,
115 identifier = %state.identifier,
116 author = %event.pubkey.to_hex(),
117 authorized_for_owners = ?authorized_owners,
118 "State event author authorized via maintainer set"
119 );
120
81 // Duplicate check in db 121 // Duplicate check in db
82 if db_repo_data.states.iter().any(|e| e.event.id.eq(&event.id)) { 122 if db_repo_data.states.iter().any(|e| e.event.id.eq(&event.id)) {
83 tracing::debug!("processed state event duplicate (in db): {}", event.id); 123 tracing::debug!("processed state event duplicate (in db): {}", event.id);