Skip to content

Commit 6bafaa6

Browse files
authored
fix(bounties): add tests for claim_bounty repo-read gate (#160) (#169)
* fix(node): gate GET /ipfs/{cid} on reachable allowed-set, not deny-set (#126) The IPFS visibility gate used withheld_blob_oids (a deny-set enumerating only reachable blobs), so a dangling/unreachable blob was absent from the set and served in cleartext to anonymous callers. Flip to an allowed-set (allowed_blob_set_for_caller) that enumerates reachable blobs the caller may read: a dangling blob has no path, is never in the set, and 404s. * perf(ipfs): check object existence before allowed-blob walk Move store::read_object before the allowed_blob_set_for_caller spawn_blocking call so random-CID spray against repos with path-scoped rules cannot trigger full-history git walks on repos that don't carry the object. * refactor(ipfs): improve formatting and readability in get_by_cid function ✓ P3 blocker fixed: cargo fmt applied — the format gate will pass. ✓ P3 cleanup resolved: withheld_blob_oids is still used by replication code in repos.rs, so it stays. • P2 follow-up: Tree/commit disclosure tracked in #135 — out of scope here. * refactor(ipfs): streamline object retrieval by separating type and content reading * docs(ipfs): update get_by_cid comment to reflect split object retrieval * Run cargo fmt on store.rs * fix(bounties): add tests for claim_bounty repo-read gate (#160) * fix(profiles): remove redundant borrow in format arg --------- Co-authored-by: Gravirei <gravirei@users.noreply.github.com>
1 parent 26bc3f6 commit 6bafaa6

2 files changed

Lines changed: 101 additions & 1 deletion

File tree

‎crates/gitlawb-node/src/api/profiles.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ pub async fn set_profile(
9898
&state.http_client,
9999
&state.config.pinata_upload_url,
100100
&state.config.pinata_jwt,
101-
&format!("profile-{}", &did),
101+
&format!("profile-{}", did),
102102
&profile_json,
103103
)
104104
.await

‎crates/gitlawb-node/src/test_support.rs‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3547,4 +3547,104 @@ mod tests {
35473547
.unwrap();
35483548
assert!(resp.status().is_success());
35493549
}
3550+
3551+
#[sqlx::test]
3552+
async fn claim_bounty_gate_denies_non_reader_on_private(pool: PgPool) {
3553+
let state = test_state(pool).await;
3554+
let owner = "did:key:zCLAIMDENYOWNERRRRRRRRRRRRRRRRRRRRRRRRR";
3555+
state
3556+
.db
3557+
.create_repo(&seed_private_repo(owner, "secret-repo"))
3558+
.await
3559+
.unwrap();
3560+
let bounty = crate::db::BountyRecord {
3561+
id: "claim-bounty-deny".into(),
3562+
repo_owner: owner.into(),
3563+
repo_name: "secret-repo".into(),
3564+
issue_id: None,
3565+
title: "Secret Claim Bounty".into(),
3566+
amount: 100,
3567+
creator_did: owner.into(),
3568+
claimant_did: None,
3569+
claimant_wallet: None,
3570+
pr_id: None,
3571+
status: "open".into(),
3572+
created_at: "2026-01-01T00:00:00Z".into(),
3573+
claimed_at: None,
3574+
submitted_at: None,
3575+
completed_at: None,
3576+
deadline_secs: 86400,
3577+
tx_hash: None,
3578+
};
3579+
state.db.create_bounty(&bounty).await.unwrap();
3580+
3581+
// A stranger (not repo owner/reader) tries to claim the bounty
3582+
let stranger_kp = gitlawb_core::identity::Keypair::generate();
3583+
let uri = "/api/v1/bounties/claim-bounty-deny/claim";
3584+
let body = b"{}";
3585+
let sig = gitlawb_core::http_sig::sign_request(&stranger_kp, "POST", uri, body);
3586+
let req = Request::builder()
3587+
.method(Method::POST)
3588+
.uri(uri)
3589+
.header("content-type", "application/json")
3590+
.header("content-digest", sig.content_digest)
3591+
.header("signature-input", sig.signature_input)
3592+
.header("signature", sig.signature)
3593+
.body(Body::from(body.to_vec()))
3594+
.unwrap();
3595+
3596+
let router = crate::server::build_router(state);
3597+
let resp = router.oneshot(req).await.unwrap();
3598+
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
3599+
}
3600+
3601+
#[sqlx::test]
3602+
async fn claim_bounty_gate_admits_owner_on_private(pool: PgPool) {
3603+
let state = test_state(pool).await;
3604+
let kp = gitlawb_core::identity::Keypair::generate();
3605+
let owner = kp.did().to_string();
3606+
state
3607+
.db
3608+
.create_repo(&seed_private_repo(&owner, "secret-repo"))
3609+
.await
3610+
.unwrap();
3611+
let bounty = crate::db::BountyRecord {
3612+
id: "claim-bounty-admit".into(),
3613+
repo_owner: owner.clone(),
3614+
repo_name: "secret-repo".into(),
3615+
issue_id: None,
3616+
title: "Owner Claim Bounty".into(),
3617+
amount: 200,
3618+
creator_did: owner.clone(),
3619+
claimant_did: None,
3620+
claimant_wallet: None,
3621+
pr_id: None,
3622+
status: "open".into(),
3623+
created_at: "2026-01-01T00:00:00Z".into(),
3624+
claimed_at: None,
3625+
submitted_at: None,
3626+
completed_at: None,
3627+
deadline_secs: 86400,
3628+
tx_hash: None,
3629+
};
3630+
state.db.create_bounty(&bounty).await.unwrap();
3631+
3632+
// The owner claims their own bounty
3633+
let uri = "/api/v1/bounties/claim-bounty-admit/claim";
3634+
let body = b"{}";
3635+
let sig = gitlawb_core::http_sig::sign_request(&kp, "POST", uri, body);
3636+
let req = Request::builder()
3637+
.method(Method::POST)
3638+
.uri(uri)
3639+
.header("content-type", "application/json")
3640+
.header("content-digest", sig.content_digest)
3641+
.header("signature-input", sig.signature_input)
3642+
.header("signature", sig.signature)
3643+
.body(Body::from(body.to_vec()))
3644+
.unwrap();
3645+
3646+
let router = crate::server::build_router(state);
3647+
let resp = router.oneshot(req).await.unwrap();
3648+
assert!(resp.status().is_success());
3649+
}
35503650
}

0 commit comments

Comments
 (0)