diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 12:40:31 +0000 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-11-28 12:40:31 +0000 |
| commit | b94262161df99966fbb8aa6861fb46603039111f (patch) | |
| tree | f0194656783d05263be2d940f4e182b1bec75070 /src | |
| parent | bf51a082ad54815f108bb255cf258fcae4a9bb4f (diff) | |
allow push to ref/nostr/<event-id>
Diffstat (limited to 'src')
| -rw-r--r-- | src/git/authorization.rs | 18 | ||||
| -rw-r--r-- | src/git/handlers.rs | 35 |
2 files changed, 50 insertions, 3 deletions
diff --git a/src/git/authorization.rs b/src/git/authorization.rs index 1be3de9..bb3bd01 100644 --- a/src/git/authorization.rs +++ b/src/git/authorization.rs | |||
| @@ -29,7 +29,7 @@ | |||
| 29 | 29 | ||
| 30 | use anyhow::{anyhow, Result}; | 30 | use anyhow::{anyhow, Result}; |
| 31 | use nostr_relay_builder::prelude::*; | 31 | use nostr_relay_builder::prelude::*; |
| 32 | use nostr_sdk::ToBech32; | 32 | use nostr_sdk::{EventId, ToBech32}; |
| 33 | use std::collections::{HashMap, HashSet}; | 33 | use std::collections::{HashMap, HashSet}; |
| 34 | use std::sync::Arc; | 34 | use std::sync::Arc; |
| 35 | use tracing::debug; | 35 | use tracing::debug; |
| @@ -647,7 +647,21 @@ pub fn validate_push_refs( | |||
| 647 | 647 | ||
| 648 | // refs/nostr/* is handled separately per GRASP-01 | 648 | // refs/nostr/* is handled separately per GRASP-01 |
| 649 | if ref_name.starts_with("refs/nostr/") { | 649 | if ref_name.starts_with("refs/nostr/") { |
| 650 | debug!("refs/nostr/ push will be validated separately"); | 650 | // Extract event_id from "refs/nostr/<event-id>" |
| 651 | if let Some(event_id_str) = ref_name.strip_prefix("refs/nostr/") { | ||
| 652 | // Validate it parses as a valid EventId | ||
| 653 | if EventId::parse(event_id_str).is_err() { | ||
| 654 | return Err(anyhow!( | ||
| 655 | "Invalid event ID format in ref: {}. Expected valid nostr event ID.", | ||
| 656 | ref_name | ||
| 657 | )); | ||
| 658 | } | ||
| 659 | // Valid EventId format - allow push (skip state event check) | ||
| 660 | debug!("refs/nostr/{} push authorized (valid EventId)", event_id_str); | ||
| 661 | continue; // Skip the rest of ref validation for this ref | ||
| 662 | } else { | ||
| 663 | return Err(anyhow!("Invalid refs/nostr/ format: {}", ref_name)); | ||
| 664 | } | ||
| 651 | } | 665 | } |
| 652 | } | 666 | } |
| 653 | 667 | ||
diff --git a/src/git/handlers.rs b/src/git/handlers.rs index 7974d8a..23d4b5b 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs | |||
| @@ -7,6 +7,7 @@ use std::sync::Arc; | |||
| 7 | use hyper::{body::Bytes, Response, StatusCode}; | 7 | use hyper::{body::Bytes, Response, StatusCode}; |
| 8 | use http_body_util::Full; | 8 | use http_body_util::Full; |
| 9 | use nostr_relay_builder::prelude::MemoryDatabase; | 9 | use nostr_relay_builder::prelude::MemoryDatabase; |
| 10 | use nostr_sdk::EventId; | ||
| 10 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; | 11 | use tokio::io::{AsyncReadExt, AsyncWriteExt}; |
| 11 | use tracing::{debug, error, info, warn}; | 12 | use tracing::{debug, error, info, warn}; |
| 12 | 13 | ||
| @@ -315,7 +316,39 @@ async fn authorize_push( | |||
| 315 | identifier, owner_pubkey | 316 | identifier, owner_pubkey |
| 316 | ); | 317 | ); |
| 317 | 318 | ||
| 318 | // Get authorization result from database, scoped to specific owner | 319 | // Parse refs from the push request FIRST to check if this is a refs/nostr/ push |
| 320 | let pushed_refs = parse_pushed_refs(request_body); | ||
| 321 | debug!("Parsed {} refs from push request", pushed_refs.len()); | ||
| 322 | for (old_oid, new_oid, ref_name) in &pushed_refs { | ||
| 323 | debug!(" {} {} -> {}", ref_name, old_oid, new_oid); | ||
| 324 | } | ||
| 325 | |||
| 326 | // Check if ALL pushed refs are to refs/nostr/ with valid EventId format | ||
| 327 | // Per GRASP-01: "MUST accept pushes via this service to `refs/nostr/<event-id>`" | ||
| 328 | // These pushes only require EventId format validation, not state validation | ||
| 329 | let all_refs_nostr_valid = !pushed_refs.is_empty() | ||
| 330 | && pushed_refs.iter().all(|(_, _, ref_name)| { | ||
| 331 | if let Some(event_id_str) = ref_name.strip_prefix("refs/nostr/") { | ||
| 332 | // Validate it parses as a valid EventId | ||
| 333 | EventId::parse(event_id_str).is_ok() | ||
| 334 | } else { | ||
| 335 | false | ||
| 336 | } | ||
| 337 | }); | ||
| 338 | |||
| 339 | if all_refs_nostr_valid { | ||
| 340 | debug!("All refs are refs/nostr/ with valid EventId format - authorized without state check"); | ||
| 341 | // Return success for refs/nostr/ pushes without requiring state | ||
| 342 | return Ok(AuthorizationResult { | ||
| 343 | authorized: true, | ||
| 344 | reason: "Push to refs/nostr/ with valid EventId format".to_string(), | ||
| 345 | state: None, | ||
| 346 | maintainers: vec![], | ||
| 347 | }); | ||
| 348 | } | ||
| 349 | |||
| 350 | // For non-refs/nostr/ pushes, require state validation as normal | ||
| 351 | debug!("Non-refs/nostr/ push detected - checking state authorization"); | ||
| 319 | let auth_result = get_authorization_for_owner(database, identifier, owner_pubkey).await?; | 352 | let auth_result = get_authorization_for_owner(database, identifier, owner_pubkey).await?; |
| 320 | 353 | ||
| 321 | if !auth_result.authorized { | 354 | if !auth_result.authorized { |