a0e2e424
add SSH authorized_keys parser
a73x 2026-03-30 18:40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
diff --git a/src/server/main.rs b/src/server/main.rs index 77871b0..46b49aa 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -1,5 +1,6 @@ mod config; mod repos; mod ssh; fn main() { println!("git-collab-server: not yet implemented"); diff --git a/src/server/ssh/auth.rs b/src/server/ssh/auth.rs new file mode 100644 index 0000000..be26001 --- /dev/null +++ b/src/server/ssh/auth.rs @@ -0,0 +1,82 @@ use std::path::Path; #[derive(Debug, Clone)] pub struct AuthorizedKey { pub key_type: String, pub key_data: String, pub comment: Option<String>, } pub fn parse_authorized_keys(content: &str) -> Vec<AuthorizedKey> { content .lines() .filter_map(|line| { let line = line.trim(); if line.is_empty() || line.starts_with('#') { return None; } let mut parts = line.splitn(3, ' '); let key_type = parts.next()?.to_string(); let key_data = parts.next()?.to_string(); let comment = parts.next().map(|s| s.to_string()); Some(AuthorizedKey { key_type, key_data, comment }) }) .collect() } pub fn load_authorized_keys(path: &Path) -> Result<Vec<AuthorizedKey>, std::io::Error> { let content = std::fs::read_to_string(path)?; Ok(parse_authorized_keys(&content)) } pub fn is_authorized(keys: &[AuthorizedKey], key_type: &str, key_data: &str) -> bool { keys.iter().any(|k| k.key_type == key_type && k.key_data == key_data) } #[cfg(test)] mod tests { use super::*; #[test] fn parse_single_key() { let content = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest user@host\n"; let keys = parse_authorized_keys(content); assert_eq!(keys.len(), 1); assert_eq!(keys[0].key_type, "ssh-ed25519"); assert_eq!(keys[0].key_data, "AAAAC3NzaC1lZDI1NTE5AAAAITest"); assert_eq!(keys[0].comment.as_deref(), Some("user@host")); } #[test] fn parse_multiple_keys() { let content = "# alice\nssh-ed25519 AAAA1111 alice@example.com\n\n# bob\nssh-ed25519 AAAA2222 bob@work\n"; let keys = parse_authorized_keys(content); assert_eq!(keys.len(), 2); assert_eq!(keys[0].key_data, "AAAA1111"); assert_eq!(keys[1].key_data, "AAAA2222"); } #[test] fn skip_comments_and_blanks() { let content = "# this is a comment\n\n # indented comment\n\nssh-ed25519 AAAAkey user\n"; let keys = parse_authorized_keys(content); assert_eq!(keys.len(), 1); } #[test] fn key_without_comment() { let content = "ssh-ed25519 AAAAnocomment\n"; let keys = parse_authorized_keys(content); assert_eq!(keys.len(), 1); assert!(keys[0].comment.is_none()); } #[test] fn is_authorized_matches() { let keys = parse_authorized_keys("ssh-ed25519 AAAA1111 alice\nssh-ed25519 AAAA2222 bob\n"); assert!(is_authorized(&keys, "ssh-ed25519", "AAAA1111")); assert!(is_authorized(&keys, "ssh-ed25519", "AAAA2222")); assert!(!is_authorized(&keys, "ssh-ed25519", "AAAA9999")); assert!(!is_authorized(&keys, "ssh-rsa", "AAAA1111")); } } diff --git a/src/server/ssh/mod.rs b/src/server/ssh/mod.rs new file mode 100644 index 0000000..0e4a05d --- /dev/null +++ b/src/server/ssh/mod.rs @@ -0,0 +1 @@ pub mod auth;