From b13c6d924f7de5ff34405254b8bb21adf33c78c0 Mon Sep 17 00:00:00 2001 From: DanConwayDev Date: Thu, 26 Feb 2026 12:19:06 +0000 Subject: send auth rejection reason to git client via ERR pkt-line Previously push auth failures returned HTTP 403 which git clients display as a generic transport error. Now they return HTTP 200 with an ERR pkt-line containing the rejection reason (e.g. 'authorisation failed: No state events in purgatory'), which git displays directly. Remove GitError::Unauthorized as it is no longer used. GitError variants now represent only transport/infrastructure failures; app-level rejections use ERR pkt-line responses. --- CHANGELOG.md | 4 ++++ src/git/handlers.rs | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 613bdf8..b613097 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Push auth rejections now send the reason to the git client via ERR pkt-line (e.g. "authorisation failed: No state events in purgatory") instead of a generic HTTP 403, so users see actionable error messages directly in their terminal + ## [1.0.0] - 2026-02-26 Initial release of ngit-grasp, a GRASP relay implementation in Rust. diff --git a/src/git/handlers.rs b/src/git/handlers.rs index f43cbb6..5ff3a7f 100644 --- a/src/git/handlers.rs +++ b/src/git/handlers.rs @@ -288,7 +288,10 @@ pub async fn handle_receive_pack( Ok(auth_result) => { if !auth_result.authorized { warn!("Push rejected for {}: {}", identifier, auth_result.reason); - return Err(GitError::Unauthorized); + return Ok(build_git_protocol_error_response( + GitService::ReceivePack, + &format!("authorisation failed: {}", auth_result.reason), + )); } info!( "Push authorized for {} - {} maintainers, {} purgatory events: {}", @@ -301,7 +304,10 @@ pub async fn handle_receive_pack( } Err(e) => { warn!("Authorization check failed for {}: {}", identifier, e); - return Err(GitError::Unauthorized); + return Ok(build_git_protocol_error_response( + GitService::ReceivePack, + &format!("authorisation failed: {}", e), + )); } }; @@ -442,13 +448,16 @@ pub async fn handle_receive_pack( } /// Errors that can occur in Git handlers +/// +/// These represent transport/infrastructure failures, not application-level +/// rejections. Application-level rejections (e.g. auth failures) are returned +/// as HTTP 200 with an ERR pkt-line so git clients can display the message. #[derive(Debug)] pub enum GitError { RepositoryNotFound, ProcessSpawnFailed(std::io::Error), IoError(std::io::Error), GitFailed(Option), - Unauthorized, } impl std::fmt::Display for GitError { @@ -458,7 +467,6 @@ impl std::fmt::Display for GitError { Self::ProcessSpawnFailed(e) => write!(f, "failed to spawn git process: {}", e), Self::IoError(e) => write!(f, "IO error: {}", e), Self::GitFailed(code) => write!(f, "git process failed with code: {:?}", code), - Self::Unauthorized => write!(f, "unauthorized"), } } } @@ -470,7 +478,6 @@ impl GitError { pub fn status_code(&self) -> StatusCode { match self { Self::RepositoryNotFound => StatusCode::NOT_FOUND, - Self::Unauthorized => StatusCode::FORBIDDEN, _ => StatusCode::INTERNAL_SERVER_ERROR, } } -- cgit v1.2.3