upleb.uk

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

summaryrefslogtreecommitdiff
path: root/src/config.rs
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 /src/config.rs
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
Diffstat (limited to 'src/config.rs')
-rw-r--r--src/config.rs143
1 files changed, 143 insertions, 0 deletions
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}