use std::str::FromStr; use models::users::user; use models::users::user_ssh_keys; use russh::keys::PublicKey; use russh::server::Auth; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, Set}; use time::OffsetDateTime; use tokio::task; use uuid::Uuid; use crate::ssh::handler::GitSshHandler; struct SshKeyUser { user: user::Model, key_id: Uuid, } impl GitSshHandler { async fn find_user_by_public_key( &self, username: &str, public_key: &PublicKey, ) -> Result, sea_orm::DbErr> { let db = self.service.smart.git.db_read(); let Some(user) = user::Entity::find() .filter(user::Column::LowerName.eq(username.to_lowercase())) .one(db) .await? else { return Ok(None); }; if !user.is_active { return Ok(None); } let keys = user_ssh_keys::Entity::find() .filter(user_ssh_keys::Column::UserId.eq(user.uid)) .all(db) .await?; let presented = public_key.to_string(); for key in keys { let matched = PublicKey::from_str(key.content.trim()) .ok() .map_or_else( || key.content.trim() == presented.trim(), |p| p == *public_key, ); if matched { return Ok(Some(SshKeyUser { user, key_id: key.id, })); } } Ok(None) } fn update_key_last_used(&self, key_id: Uuid) { let db = self.service.smart.git.state.database.clone(); let now = OffsetDateTime::now_utc(); task::spawn(async move { let _ = user_ssh_keys::Entity::update_many() .set(user_ssh_keys::ActiveModel { id: Set(key_id), last_used_at: Set(Some(now)), ..Default::default() }) .filter(user_ssh_keys::Column::Id.eq(key_id)) .exec(db.write()) .await; }); } pub async fn auth_publickey_offered_impl( &mut self, user: &str, public_key: &PublicKey, ) -> Result { if self.over_capacity { return Ok(Auth::reject()); } if user.is_empty() || public_key.to_string().len() < 32 { return Ok(Auth::reject()); } match self.find_user_by_public_key(user, public_key).await { Ok(Some(_)) => Ok(Auth::Accept), Ok(None) => Ok(Auth::reject()), Err(e) => { tracing::warn!("ssh auth lookup failed: {e}"); Err(russh::Error::NotAuthenticated) } } } pub async fn auth_publickey_impl( &mut self, user: &str, public_key: &PublicKey, ) -> Result { if self.over_capacity { return Ok(Auth::reject()); } if user.is_empty() || public_key.to_string().len() < 32 { return Ok(Auth::reject()); } match self.find_user_by_public_key(user, public_key).await { Ok(Some(found)) => { if found.user.is_restricted || found.user.prohibit_login { return Ok(Auth::reject()); } let key_id = found.key_id; self.operator = Some(found.user); self.update_key_last_used(key_id); Ok(Auth::Accept) } Ok(None) => Ok(Auth::reject()), Err(e) => { tracing::warn!("ssh auth lookup failed: {e}"); Err(russh::Error::NotAuthenticated) } } } }