upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 21:32:38 +0000
committerDanConwayDev <DanConwayDev@protonmail.com>2026-01-12 21:33:15 +0000
commit70c577f10bbe150b6b13bec545dc8720ad005a64 (patch)
tree4f390cd523248db007ecb4335a61598b930ccad9
parent1948312d40f34fca868d1ef6d6d94e165c09738c (diff)
feat(config): add repository blacklist to block specific repos/npubs/identifiers
Adds NGIT_REPOSITORY_BLACKLIST option for blocking repositories, taking precedence over all whitelists (archive and repository) to enable moderation without affecting curation policy. Key features: - Three blacklist formats: <npub>, <npub>/<identifier>, <identifier> - Blacklist checked first before any other validation - Overrides archive whitelist and repository whitelist - Specific rejection reasons based on match type (npub/identifier/both) - Not flagged in NIP-11 curation (operational, not policy) Implementation: - Add BlacklistConfig struct with check() method returning detailed reasons - Add NGIT_REPOSITORY_BLACKLIST config option and blacklist_config() method - Update validate_announcement() to check blacklist first with specific reasons - 12 new unit tests covering all blacklist behavior and precedence Configuration synced across all four sources: - src/config.rs: Core implementation with BlacklistConfig - .env.example: Comprehensive documentation with examples - docs/reference/configuration.md: Complete reference documentation - nix/module.nix: NixOS module option with environment mapping Testing: - 12 new tests for blacklist functionality (config + validation) - All 332 library tests passing - All 38 integration tests passing Use cases: - Block spam/malware repos by identifier - Block abusive users by npub - Block specific problematic repos by npub/identifier - Temporary blocks for investigation
-rw-r--r--.env.example29
-rw-r--r--README.md2
-rw-r--r--docs/reference/configuration.md89
-rw-r--r--nix/module.nix14
-rw-r--r--src/config.rs143
-rw-r--r--src/nostr/events.rs187
6 files changed, 462 insertions, 2 deletions
diff --git a/.env.example b/.env.example
index 0789b28..993399a 100644
--- a/.env.example
+++ b/.env.example
@@ -227,4 +227,31 @@
227# NGIT_REPOSITORY_WHITELIST=bitcoin-core,linux,rust 227# NGIT_REPOSITORY_WHITELIST=bitcoin-core,linux,rust
228# Note: Cannot be used with NGIT_ARCHIVE_READ_ONLY=true (mutually exclusive) 228# Note: Cannot be used with NGIT_ARCHIVE_READ_ONLY=true (mutually exclusive)
229# Note: When set, NIP-11 curation field will indicate curated repository acceptance 229# Note: When set, NIP-11 curation field will indicate curated repository acceptance
230# NGIT_REPOSITORY_WHITELIST= \ No newline at end of file 230# NGIT_REPOSITORY_WHITELIST=
231
232# ============================================================================
233# REPOSITORY BLACKLIST
234# ============================================================================
235
236# Blacklist specific repos/pubkeys/identifiers to reject
237# Comma-separated list supporting three formats (same as whitelist formats):
238# <npub> - Block all repos from this pubkey
239# <npub>/<identifier> - Block specific repo
240# <identifier> - Block repos with this identifier (any pubkey)
241#
242# Blacklist takes precedence over ALL whitelists:
243# - Blacklisted repos are rejected even if they match archive or repository whitelists
244# - Blacklisted repos are rejected even if they list our service
245#
246# Rejection reasons indicate the match type:
247# - "Repository owner <npub> is blacklisted" (npub format)
248# - "Repository <npub>/<identifier> is blacklisted" (npub/identifier format)
249# - "Repository identifier <identifier> is blacklisted" (identifier format)
250#
251# CLI: --repository-blacklist <list>
252# Default: (empty - no repositories are blacklisted)
253# Examples:
254# NGIT_REPOSITORY_BLACKLIST=npub1spam...
255# NGIT_REPOSITORY_BLACKLIST=npub1alice.../bad-repo
256# NGIT_REPOSITORY_BLACKLIST=malware-repo,spam-repo
257# NGIT_REPOSITORY_BLACKLIST= \ No newline at end of file
diff --git a/README.md b/README.md
index c9d1066..50bee24 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@ Unlike the reference implementation ([ngit-relay](https://gitworkshop.dev/npub15
36- **Pure Rust Implementation**: Single binary, no external dependencies beyond Git itself 36- **Pure Rust Implementation**: Single binary, no external dependencies beyond Git itself
37- **Integrated Authorization**: Push validation happens inline during the Git receive-pack operation 37- **Integrated Authorization**: Push validation happens inline during the Git receive-pack operation
38- **GRASP-01 Compliant**: Core service requirements for Git hosting with Nostr authorization 38- **GRASP-01 Compliant**: Core service requirements for Git hosting with Nostr authorization
39 - **Repository Whitelist**: Optional curation via pubkey/identifier whitelist (GRASP-01 mode) 39 - **Repository Whitelist/Blacklist**: Optional curation via pubkey/identifier whitelist (GRASP-01 mode) and blacklist (overrides all whitelists)
40- **GRASP-02 Proactive Sync**: Sophisticated relay-to-relay event and git data synchronization 40- **GRASP-02 Proactive Sync**: Sophisticated relay-to-relay event and git data synchronization
41 - **NIP-77 Negentropy**: Efficient set reconciliation with automatic fallback to REQ+EOSE 41 - **NIP-77 Negentropy**: Efficient set reconciliation with automatic fallback to REQ+EOSE
42 - **Live & Historic Sync**: Real-time event streaming plus catch-up for past events 42 - **Live & Historic Sync**: Real-time event streaming plus catch-up for past events
diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md
index 1c62911..b90686e 100644
--- a/docs/reference/configuration.md
+++ b/docs/reference/configuration.md
@@ -744,6 +744,95 @@ NGIT_REPOSITORY_WHITELIST=bitcoin-core,npub1alice...
744 744
745--- 745---
746 746
747### Repository Blacklist
748
749#### `NGIT_REPOSITORY_BLACKLIST`
750
751**Description:** Blacklist specific repositories/pubkeys/identifiers to reject
752**Type:** Comma-separated list
753**Default:** Empty (no repositories are blacklisted)
754**Required:** No
755
756**Format:** Same as whitelist formats:
757- `npub1...` - Block all repos from this pubkey
758- `npub1.../identifier` - Block specific repo
759- `identifier` - Block repos with this identifier (any pubkey)
760
761**Precedence:** Blacklist takes precedence over **ALL** whitelists:
762- Blacklisted repos are rejected even if they match archive or repository whitelists
763- Blacklisted repos are rejected even if they list our service
764- Blacklist is checked **first** before any other validation
765
766**Examples:**
767
768```bash
769# Block all repos from specific pubkey
770NGIT_REPOSITORY_BLACKLIST=npub1spam...
771
772# Block specific repo
773NGIT_REPOSITORY_BLACKLIST=npub1alice.../malware-repo
774
775# Block repos with specific identifiers
776NGIT_REPOSITORY_BLACKLIST=malware,spam,phishing
777
778# Combined blacklist
779NGIT_REPOSITORY_BLACKLIST=npub1spam...,npub1alice.../bad-repo,malware
780```
781
782**Rejection Reasons:**
783
784The blacklist provides specific rejection reasons based on the match type:
785
786- **Npub format:** `"Repository owner <npub> is blacklisted"`
787- **Npub/identifier format:** `"Repository <npub>/<identifier> is blacklisted"`
788- **Identifier format:** `"Repository identifier <identifier> is blacklisted"`
789
790These reasons help operators understand why a repository was rejected without needing to flag it in curation metadata.
791
792**Behavior:**
793
794Blacklist is checked **before** all other validation:
7951. Check blacklist → Reject if matched
7962. Check if lists service → Accept if matches repository whitelist (if enabled)
7973. Check archive config → Accept if matches archive whitelist (if enabled)
7984. Reject otherwise
799
800**Use Cases:**
801
802```bash
803# Block spam/malware repos
804NGIT_REPOSITORY_BLACKLIST=malware,spam,phishing
805
806# Block abusive users
807NGIT_REPOSITORY_BLACKLIST=npub1spammer...,npub1abuser...
808
809# Block specific problematic repos
810NGIT_REPOSITORY_BLACKLIST=npub1alice.../copyright-violation,npub1bob.../illegal-content
811
812# Temporary block for investigation
813NGIT_REPOSITORY_BLACKLIST=npub1suspicious.../repo-under-review
814```
815
816**Comparison with Whitelists:**
817
818| Configuration | Blacklisted? | Matches Whitelist? | Lists Service? | Result |
819|---------------|--------------|-------------------|----------------|---------|
820| Blacklist only | Yes | N/A | N/A | ❌ Reject (blacklisted) |
821| Blacklist only | No | N/A | Yes | ✅ Accept (GRASP-01) |
822| Blacklist + Repository whitelist | Yes | Yes | Yes | ❌ Reject (blacklist wins) |
823| Blacklist + Archive whitelist | Yes | Yes | No | ❌ Reject (blacklist wins) |
824| Blacklist + Both whitelists | Yes | Yes | Yes | ❌ Reject (blacklist wins) |
825| Blacklist only | No | N/A | No | ❌ Reject (no whitelist match) |
826
827**NIP-11 Impact:**
828
829Blacklist does **not** affect NIP-11 metadata:
830- No `curation` field changes (blacklist is operational, not curation policy)
831- Blacklist is transparent to clients (rejected with specific reason)
832- Operators can use blacklist without advertising curation
833
834---
835
747### Logging Configuration 836### Logging Configuration
748 837
749#### `RUST_LOG` 838#### `RUST_LOG`
diff --git a/nix/module.nix b/nix/module.nix
index d5dfd88..cfac0fc 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -224,6 +224,19 @@ let
224 ''; 224 '';
225 }; 225 };
226 226
227 repositoryBlacklist = mkOption {
228 type = types.listOf types.str;
229 default = [ ];
230 example = [ "npub1spam..." "npub1alice.../bad-repo" "malware" ];
231 description = ''
232 Repository blacklist for blocking specific repositories/pubkeys/identifiers.
233 Blacklist takes precedence over ALL whitelists (archive and repository).
234 Formats: <npub>, <npub>/<identifier>, <identifier>
235 Blacklisted repos are rejected with specific reasons (npub/identifier/both).
236 Does not affect NIP-11 curation field (operational, not curation policy).
237 '';
238 };
239
227 user = mkOption { 240 user = mkOption {
228 type = types.str; 241 type = types.str;
229 default = "ngit-grasp-${name}"; 242 default = "ngit-grasp-${name}";
@@ -267,6 +280,7 @@ let
267 NGIT_ARCHIVE_ALL = toString cfg.archiveAll; 280 NGIT_ARCHIVE_ALL = toString cfg.archiveAll;
268 NGIT_ARCHIVE_WHITELIST = concatStringsSep "," cfg.archiveWhitelist; 281 NGIT_ARCHIVE_WHITELIST = concatStringsSep "," cfg.archiveWhitelist;
269 NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist; 282 NGIT_REPOSITORY_WHITELIST = concatStringsSep "," cfg.repositoryWhitelist;
283 NGIT_REPOSITORY_BLACKLIST = concatStringsSep "," cfg.repositoryBlacklist;
270 RUST_LOG = cfg.logLevel; 284 RUST_LOG = cfg.logLevel;
271 } // optionalAttrs (cfg.relayName != null) { 285 } // optionalAttrs (cfg.relayName != null) {
272 NGIT_RELAY_NAME = cfg.relayName; 286 NGIT_RELAY_NAME = cfg.relayName;
diff --git a/src/config.rs b/src/config.rs
index 37b1c1e..5f8cbca 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -195,6 +195,55 @@ impl Default for RepositoryConfig {
195 } 195 }
196} 196}
197 197
198/// Repository blacklist configuration
199#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct BlacklistConfig {
201 /// Blacklist entries for blocking specific repositories
202 ///
203 /// If empty, no repositories are blacklisted.
204 /// Blacklist takes precedence over both archive and repository whitelists.
205 pub blacklist: Vec<WhitelistEntry>,
206}
207
208impl BlacklistConfig {
209 /// Check if repository blacklist is enabled (non-empty blacklist)
210 pub fn enabled(&self) -> bool {
211 !self.blacklist.is_empty()
212 }
213
214 /// Check if an announcement matches the repository blacklist
215 ///
216 /// Returns Some(reason) if blacklisted, None if not blacklisted.
217 /// The reason indicates what type of match occurred (npub, npub/identifier, or identifier).
218 pub fn check(&self, npub: &str, identifier: &str) -> Option<String> {
219 for entry in &self.blacklist {
220 if entry.matches(npub, identifier) {
221 let reason = match entry {
222 WhitelistEntry::Pubkey(_) => {
223 format!("Repository owner {} is blacklisted", npub)
224 }
225 WhitelistEntry::Repository { .. } => {
226 format!("Repository {}/{} is blacklisted", npub, identifier)
227 }
228 WhitelistEntry::Identifier(_) => {
229 format!("Repository identifier {} is blacklisted", identifier)
230 }
231 };
232 return Some(reason);
233 }
234 }
235 None
236 }
237}
238
239impl Default for BlacklistConfig {
240 fn default() -> Self {
241 Self {
242 blacklist: Vec::new(),
243 }
244 }
245}
246
198/// Database backend type for the relay 247/// Database backend type for the relay
199#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)] 248#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default, ValueEnum)]
200#[serde(rename_all = "lowercase")] 249#[serde(rename_all = "lowercase")]
@@ -373,6 +422,12 @@ pub struct Config {
373 /// When set, only announcements matching the whitelist AND listing the service are accepted 422 /// When set, only announcements matching the whitelist AND listing the service are accepted
374 #[arg(long, env = "NGIT_REPOSITORY_WHITELIST", default_value = "")] 423 #[arg(long, env = "NGIT_REPOSITORY_WHITELIST", default_value = "")]
375 pub repository_whitelist: String, 424 pub repository_whitelist: String,
425
426 /// Repository blacklist: comma-separated list of npub/identifier/npub/identifier entries to reject
427 /// Formats: "npub1...", "npub1.../identifier", "identifier"
428 /// Blacklist takes precedence over all whitelists (archive and repository)
429 #[arg(long, env = "NGIT_REPOSITORY_BLACKLIST", default_value = "")]
430 pub repository_blacklist: String,
376} 431}
377 432
378impl Config { 433impl Config {
@@ -549,6 +604,14 @@ impl Config {
549 RepositoryConfig { whitelist } 604 RepositoryConfig { whitelist }
550 } 605 }
551 606
607 /// Get parsed repository blacklist configuration
608 ///
609 /// This method assumes config has been validated - call Config::validate() first!
610 pub fn blacklist_config(&self) -> BlacklistConfig {
611 let blacklist = WhitelistEntry::parse_whitelist(&self.repository_blacklist);
612 BlacklistConfig { blacklist }
613 }
614
552 /// Create config for testing 615 /// Create config for testing
553 #[cfg(test)] 616 #[cfg(test)]
554 pub fn for_testing() -> Self { 617 pub fn for_testing() -> Self {
@@ -583,6 +646,7 @@ impl Config {
583 archive_whitelist: String::new(), 646 archive_whitelist: String::new(),
584 archive_read_only: None, 647 archive_read_only: None,
585 repository_whitelist: String::new(), 648 repository_whitelist: String::new(),
649 repository_blacklist: String::new(),
586 } 650 }
587 } 651 }
588} 652}
@@ -1105,4 +1169,83 @@ mod tests {
1105 .to_string() 1169 .to_string()
1106 .contains("relay_owner_nsec not set")); 1170 .contains("relay_owner_nsec not set"));
1107 } 1171 }
1172
1173 #[test]
1174 fn test_blacklist_config_parsing() {
1175 let keys = Keys::generate();
1176 let test_npub = keys.public_key().to_bech32().unwrap();
1177 let config = Config {
1178 repository_blacklist: format!("{},bitcoin-core", test_npub),
1179 ..Config::for_testing()
1180 };
1181 let blacklist_config = config.blacklist_config();
1182 assert_eq!(blacklist_config.blacklist.len(), 2);
1183 assert!(blacklist_config.enabled());
1184 }
1185
1186 #[test]
1187 fn test_blacklist_config_empty() {
1188 let config = Config::for_testing();
1189 let blacklist_config = config.blacklist_config();
1190 assert!(blacklist_config.blacklist.is_empty());
1191 assert!(!blacklist_config.enabled());
1192 }
1193
1194 #[test]
1195 fn test_blacklist_check_npub() {
1196 let keys = Keys::generate();
1197 let test_npub = keys.public_key().to_bech32().unwrap();
1198 let config = BlacklistConfig {
1199 blacklist: vec![WhitelistEntry::Pubkey(test_npub.clone())],
1200 };
1201
1202 let result = config.check(&test_npub, "any-repo");
1203 assert!(result.is_some());
1204 let reason = result.unwrap();
1205 assert!(reason.contains("owner"));
1206 assert!(reason.contains(&test_npub));
1207 }
1208
1209 #[test]
1210 fn test_blacklist_check_identifier() {
1211 let config = BlacklistConfig {
1212 blacklist: vec![WhitelistEntry::Identifier("banned-repo".to_string())],
1213 };
1214
1215 let result = config.check("npub1alice", "banned-repo");
1216 assert!(result.is_some());
1217 let reason = result.unwrap();
1218 assert!(reason.contains("identifier"));
1219 assert!(reason.contains("banned-repo"));
1220 }
1221
1222 #[test]
1223 fn test_blacklist_check_repository() {
1224 let keys = Keys::generate();
1225 let test_npub = keys.public_key().to_bech32().unwrap();
1226 let config = BlacklistConfig {
1227 blacklist: vec![WhitelistEntry::Repository {
1228 npub: test_npub.clone(),
1229 identifier: "specific-repo".to_string(),
1230 }],
1231 };
1232
1233 let result = config.check(&test_npub, "specific-repo");
1234 assert!(result.is_some());
1235 let reason = result.unwrap();
1236 assert!(reason.contains(&test_npub));
1237 assert!(reason.contains("specific-repo"));
1238 }
1239
1240 #[test]
1241 fn test_blacklist_check_not_blacklisted() {
1242 let keys = Keys::generate();
1243 let test_npub = keys.public_key().to_bech32().unwrap();
1244 let config = BlacklistConfig {
1245 blacklist: vec![WhitelistEntry::Identifier("banned-repo".to_string())],
1246 };
1247
1248 let result = config.check(&test_npub, "allowed-repo");
1249 assert!(result.is_none());
1250 }
1108} 1251}
diff --git a/src/nostr/events.rs b/src/nostr/events.rs
index 3b4ef25..39014da 100644
--- a/src/nostr/events.rs
+++ b/src/nostr/events.rs
@@ -366,6 +366,9 @@ impl RepositoryState {
366/// - AcceptArchive: Announcement matches archive config (GRASP-05) 366/// - AcceptArchive: Announcement matches archive config (GRASP-05)
367/// - Reject: Validation failed 367/// - Reject: Validation failed
368/// 368///
369/// Blacklist takes precedence over all whitelists:
370/// - If blacklisted, always reject with specific reason (npub/identifier/npub+identifier)
371///
369/// When archive_read_only is true: 372/// When archive_read_only is true:
370/// - ONLY accept announcements matching archive whitelist/all 373/// - ONLY accept announcements matching archive whitelist/all
371/// - REJECT announcements listing our service but not in whitelist (read-only sync mode) 374/// - REJECT announcements listing our service but not in whitelist (read-only sync mode)
@@ -403,10 +406,16 @@ pub fn validate_announcement(
403 // Get validated configs (config.validate() must be called at startup) 406 // Get validated configs (config.validate() must be called at startup)
404 let archive_config = config.archive_config(); 407 let archive_config = config.archive_config();
405 let repository_config = config.repository_config(); 408 let repository_config = config.repository_config();
409 let blacklist_config = config.blacklist_config();
406 410
407 let npub = announcement.owner_npub(); 411 let npub = announcement.owner_npub();
408 let lists_service = announcement.lists_service(&config.domain); 412 let lists_service = announcement.lists_service(&config.domain);
409 413
414 // Check blacklist FIRST - it overrides everything
415 if let Some(reason) = blacklist_config.check(&npub, &announcement.identifier) {
416 return AnnouncementResult::Reject(reason);
417 }
418
410 // GRASP-01: Normal mode - accept if announcement lists our service AND matches repository whitelist (if enabled) 419 // GRASP-01: Normal mode - accept if announcement lists our service AND matches repository whitelist (if enabled)
411 if lists_service && !archive_config.read_only { 420 if lists_service && !archive_config.read_only {
412 // Check repository whitelist if enabled 421 // Check repository whitelist if enabled
@@ -1309,4 +1318,182 @@ mod tests {
1309 let result = validate_announcement(&event, &config); 1318 let result = validate_announcement(&event, &config);
1310 assert!(matches!(result, AnnouncementResult::Reject(_))); 1319 assert!(matches!(result, AnnouncementResult::Reject(_)));
1311 } 1320 }
1321
1322 #[test]
1323 fn test_blacklist_rejects_npub() {
1324 use crate::config::Config;
1325 use crate::nostr::policy::AnnouncementResult;
1326
1327 let keys = create_test_keys();
1328 let npub = keys.public_key().to_bech32().unwrap();
1329
1330 // Create announcement that lists our service
1331 let event = create_announcement_event(
1332 &keys,
1333 "test-repo",
1334 vec!["https://gitnostr.com/alice/test-repo.git"],
1335 vec!["wss://gitnostr.com"],
1336 );
1337
1338 // Config with blacklist for this npub
1339 let config = Config {
1340 domain: "gitnostr.com".to_string(),
1341 repository_blacklist: npub.clone(),
1342 ..Config::for_testing()
1343 };
1344
1345 let result = validate_announcement(&event, &config);
1346 if let AnnouncementResult::Reject(reason) = result {
1347 assert!(reason.contains("owner"));
1348 assert!(reason.contains(&npub));
1349 } else {
1350 panic!("Expected Reject, got {:?}", result);
1351 }
1352 }
1353
1354 #[test]
1355 fn test_blacklist_rejects_identifier() {
1356 use crate::config::Config;
1357 use crate::nostr::policy::AnnouncementResult;
1358
1359 let keys = create_test_keys();
1360
1361 // Create announcement that lists our service
1362 let event = create_announcement_event(
1363 &keys,
1364 "banned-repo",
1365 vec!["https://gitnostr.com/alice/banned-repo.git"],
1366 vec!["wss://gitnostr.com"],
1367 );
1368
1369 // Config with blacklist for this identifier
1370 let config = Config {
1371 domain: "gitnostr.com".to_string(),
1372 repository_blacklist: "banned-repo".to_string(),
1373 ..Config::for_testing()
1374 };
1375
1376 let result = validate_announcement(&event, &config);
1377 if let AnnouncementResult::Reject(reason) = result {
1378 assert!(reason.contains("identifier"));
1379 assert!(reason.contains("banned-repo"));
1380 } else {
1381 panic!("Expected Reject, got {:?}", result);
1382 }
1383 }
1384
1385 #[test]
1386 fn test_blacklist_rejects_specific_repository() {
1387 use crate::config::Config;
1388 use crate::nostr::policy::AnnouncementResult;
1389
1390 let keys = create_test_keys();
1391 let npub = keys.public_key().to_bech32().unwrap();
1392
1393 // Create announcement that lists our service
1394 let event = create_announcement_event(
1395 &keys,
1396 "specific-repo",
1397 vec!["https://gitnostr.com/alice/specific-repo.git"],
1398 vec!["wss://gitnostr.com"],
1399 );
1400
1401 // Config with blacklist for this specific repo
1402 let config = Config {
1403 domain: "gitnostr.com".to_string(),
1404 repository_blacklist: format!("{}/specific-repo", npub),
1405 ..Config::for_testing()
1406 };
1407
1408 let result = validate_announcement(&event, &config);
1409 if let AnnouncementResult::Reject(reason) = result {
1410 assert!(reason.contains(&npub));
1411 assert!(reason.contains("specific-repo"));
1412 } else {
1413 panic!("Expected Reject, got {:?}", result);
1414 }
1415 }
1416
1417 #[test]
1418 fn test_blacklist_overrides_repository_whitelist() {
1419 use crate::config::Config;
1420 use crate::nostr::policy::AnnouncementResult;
1421
1422 let keys = create_test_keys();
1423 let npub = keys.public_key().to_bech32().unwrap();
1424
1425 // Create announcement that lists our service
1426 let event = create_announcement_event(
1427 &keys,
1428 "test-repo",
1429 vec!["https://gitnostr.com/alice/test-repo.git"],
1430 vec!["wss://gitnostr.com"],
1431 );
1432
1433 // Config with both whitelist and blacklist - blacklist should win
1434 let config = Config {
1435 domain: "gitnostr.com".to_string(),
1436 repository_whitelist: npub.clone(),
1437 repository_blacklist: npub.clone(),
1438 ..Config::for_testing()
1439 };
1440
1441 let result = validate_announcement(&event, &config);
1442 assert!(matches!(result, AnnouncementResult::Reject(_)));
1443 }
1444
1445 #[test]
1446 fn test_blacklist_overrides_archive_whitelist() {
1447 use crate::config::Config;
1448 use crate::nostr::policy::AnnouncementResult;
1449
1450 let keys = create_test_keys();
1451 let npub = keys.public_key().to_bech32().unwrap();
1452
1453 // Create announcement that does NOT list our service
1454 let event = create_announcement_event(
1455 &keys,
1456 "test-repo",
1457 vec!["https://other-service.com/alice/test-repo.git"],
1458 vec!["wss://other-service.com"],
1459 );
1460
1461 // Config with archive whitelist and blacklist - blacklist should win
1462 let config = Config {
1463 domain: "gitnostr.com".to_string(),
1464 archive_whitelist: npub.clone(),
1465 archive_read_only: Some(false),
1466 repository_blacklist: npub.clone(),
1467 ..Config::for_testing()
1468 };
1469
1470 let result = validate_announcement(&event, &config);
1471 assert!(matches!(result, AnnouncementResult::Reject(_)));
1472 }
1473
1474 #[test]
1475 fn test_blacklist_allows_non_blacklisted() {
1476 use crate::config::Config;
1477 use crate::nostr::policy::AnnouncementResult;
1478
1479 let keys = create_test_keys();
1480
1481 // Create announcement that lists our service
1482 let event = create_announcement_event(
1483 &keys,
1484 "allowed-repo",
1485 vec!["https://gitnostr.com/alice/allowed-repo.git"],
1486 vec!["wss://gitnostr.com"],
1487 );
1488
1489 // Config with blacklist for different identifier
1490 let config = Config {
1491 domain: "gitnostr.com".to_string(),
1492 repository_blacklist: "banned-repo".to_string(),
1493 ..Config::for_testing()
1494 };
1495
1496 let result = validate_announcement(&event, &config);
1497 assert!(matches!(result, AnnouncementResult::Accept));
1498 }
1312} 1499}