feat: add private repository support with SSH key and HTTPS token auth
Some checks failed
CI / Clippy (push) Failing after 2m39s
CI / Security Audit (push) Has been skipped
CI / Tests (push) Has been skipped
CI / Format (pull_request) Failing after 3s
CI / Clippy (pull_request) Failing after 2m33s
CI / Security Audit (pull_request) Has been skipped
CI / Tests (pull_request) Has been skipped
CI / Detect Changes (push) Has been skipped
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (push) Has been skipped
CI / Deploy Dashboard (push) Has been skipped
CI / Deploy Docs (push) Has been skipped
CI / Format (push) Failing after 4s
CI / Deploy MCP (pull_request) Has been skipped
CI / Deploy MCP (push) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped

- Generate SSH ed25519 key pair on agent startup for cloning private repos via SSH
- Add GET /api/v1/settings/ssh-public-key endpoint to expose deploy key
- Add auth_token and auth_username fields to TrackedRepository model
- Wire git2 credential callbacks for both SSH and HTTPS authentication
- Validate repository access before saving (test-connect on add)
- Update dashboard add form with optional auth section showing deploy key and token fields
- Show error toast if private repo cannot be accessed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-03-09 11:53:17 +01:00
parent 23ba52276b
commit 492a93a83e
13 changed files with 338 additions and 18 deletions

View File

@@ -82,6 +82,8 @@ pub struct AddRepositoryRequest {
pub git_url: String,
#[serde(default = "default_branch")]
pub default_branch: String,
pub auth_token: Option<String>,
pub auth_username: Option<String>,
pub tracker_type: Option<TrackerType>,
pub tracker_owner: Option<String>,
pub tracker_repo: Option<String>,
@@ -284,9 +286,25 @@ pub async fn list_repositories(
pub async fn add_repository(
Extension(agent): AgentExt,
Json(req): Json<AddRepositoryRequest>,
) -> Result<Json<ApiResponse<TrackedRepository>>, StatusCode> {
) -> Result<Json<ApiResponse<TrackedRepository>>, (StatusCode, String)> {
// Validate repository access before saving
let creds = crate::pipeline::git::RepoCredentials {
ssh_key_path: Some(agent.config.ssh_key_path.clone()),
auth_token: req.auth_token.clone(),
auth_username: req.auth_username.clone(),
};
if let Err(e) = crate::pipeline::git::GitOps::test_access(&req.git_url, &creds) {
return Err((
StatusCode::BAD_REQUEST,
format!("Cannot access repository: {e}"),
));
}
let mut repo = TrackedRepository::new(req.name, req.git_url);
repo.default_branch = req.default_branch;
repo.auth_token = req.auth_token;
repo.auth_username = req.auth_username;
repo.tracker_type = req.tracker_type;
repo.tracker_owner = req.tracker_owner;
repo.tracker_repo = req.tracker_repo;
@@ -297,7 +315,7 @@ pub async fn add_repository(
.repositories()
.insert_one(&repo)
.await
.map_err(|_| StatusCode::CONFLICT)?;
.map_err(|_| (StatusCode::CONFLICT, "Repository already exists".to_string()))?;
Ok(Json(ApiResponse {
data: repo,
@@ -306,6 +324,14 @@ pub async fn add_repository(
}))
}
pub async fn get_ssh_public_key(
Extension(agent): AgentExt,
) -> Result<Json<serde_json::Value>, StatusCode> {
let public_path = format!("{}.pub", agent.config.ssh_key_path);
let public_key = std::fs::read_to_string(&public_path).map_err(|_| StatusCode::NOT_FOUND)?;
Ok(Json(serde_json::json!({ "public_key": public_key.trim() })))
}
pub async fn trigger_scan(
Extension(agent): AgentExt,
Path(id): Path<String>,