From b94262161df99966fbb8aa6861fb46603039111f Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Fri, 28 Nov 2025 12:40:31 +0000 Subject: allow push to ref/nostr/ --- src/git/authorization.rs | 18 ++++++++++++++++-- src/git/handlers.rs | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) (limited to 'src/git') 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 @@ use anyhow::{anyhow, Result}; use nostr_relay_builder::prelude::*; -use nostr_sdk::ToBech32; +use nostr_sdk::{EventId, ToBech32}; use std::collections::{HashMap, HashSet}; use std::sync::Arc; use tracing::debug; @@ -647,7 +647,21 @@ pub fn validate_push_refs( // refs/nostr/* is handled separately per GRASP-01 if ref_name.starts_with("refs/nostr/") { - debug!("refs/nostr/ push will be validated separately"); + // Extract event_id from "refs/nostr/" + if let Some(event_id_str) = ref_name.strip_prefix("refs/nostr/") { + // Validate it parses as a valid EventId + if EventId::parse(event_id_str).is_err() { + return Err(anyhow!( + "Invalid event ID format in ref: {}. Expected valid nostr event ID.", + ref_name + )); + } + // Valid EventId format - allow push (skip state event check) + debug!("refs/nostr/{} push authorized (valid EventId)", event_id_str); + continue; // Skip the rest of ref validation for this ref + } else { + return Err(anyhow!("Invalid refs/nostr/ format: {}", ref_name)); + } } } 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; use hyper::{body::Bytes, Response, StatusCode}; use http_body_util::Full; use nostr_relay_builder::prelude::MemoryDatabase; +use nostr_sdk::EventId; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tracing::{debug, error, info, warn}; @@ -315,7 +316,39 @@ async fn authorize_push( identifier, owner_pubkey ); - // Get authorization result from database, scoped to specific owner + // Parse refs from the push request FIRST to check if this is a refs/nostr/ push + let pushed_refs = parse_pushed_refs(request_body); + debug!("Parsed {} refs from push request", pushed_refs.len()); + for (old_oid, new_oid, ref_name) in &pushed_refs { + debug!(" {} {} -> {}", ref_name, old_oid, new_oid); + } + + // Check if ALL pushed refs are to refs/nostr/ with valid EventId format + // Per GRASP-01: "MUST accept pushes via this service to `refs/nostr/`" + // These pushes only require EventId format validation, not state validation + let all_refs_nostr_valid = !pushed_refs.is_empty() + && pushed_refs.iter().all(|(_, _, ref_name)| { + if let Some(event_id_str) = ref_name.strip_prefix("refs/nostr/") { + // Validate it parses as a valid EventId + EventId::parse(event_id_str).is_ok() + } else { + false + } + }); + + if all_refs_nostr_valid { + debug!("All refs are refs/nostr/ with valid EventId format - authorized without state check"); + // Return success for refs/nostr/ pushes without requiring state + return Ok(AuthorizationResult { + authorized: true, + reason: "Push to refs/nostr/ with valid EventId format".to_string(), + state: None, + maintainers: vec![], + }); + } + + // For non-refs/nostr/ pushes, require state validation as normal + debug!("Non-refs/nostr/ push detected - checking state authorization"); let auth_result = get_authorization_for_owner(database, identifier, owner_pubkey).await?; if !auth_result.authorized { -- cgit v1.2.3