use futures::stream::TryStreamExt; use log::{error, info}; use rocket::fairing::{self, AdHoc}; use rocket::serde::Deserialize; use rocket::{Build, Rocket}; use rocket_db_pools::{sqlx, Database}; use std::borrow::Cow; use std::os::unix::fs::MetadataExt; use std::path::{Path, PathBuf}; use std::sync::Mutex; use tokio::fs; use tokio::sync::Semaphore; use crate::Db; #[derive(Debug, Deserialize)] pub struct Config<'a> { authorized_keys: Cow<'a, str>, } #[derive(Clone)] struct Key { id: u64, user: String, kind: String, data: String, } struct AuthorizedKeysData { keys: Vec, } pub struct AuthorizedKeys { data: Mutex, update_lock: Semaphore, } impl AuthorizedKeys { pub async fn new_user_key( &self, config: &Config<'_>, id: u64, user: &str, kind: &str, data: &str, ) { let key = Key { id, user: user.to_string(), kind: kind.to_string(), data: data.to_string(), }; let path = PathBuf::from(config.authorized_keys.to_string()); let keys = { let mut data = self.data.lock().unwrap(); data.keys.push(key); data.keys.clone() }; self.update(path.as_path(), &keys).await.unwrap(); } pub async fn del_user_key(&self, config: &Config<'_>, id: u64, user: &str) { let path = PathBuf::from(config.authorized_keys.to_string()); let keys = { let mut data = self.data.lock().unwrap(); if let Some(index) = data.keys.iter().position(|x| x.id == id && x.user == user) { data.keys.remove(index); } data.keys.clone() }; self.update(path.as_path(), &keys).await.unwrap(); } async fn update(&self, path: &Path, keys: &Vec) -> std::io::Result<()> { let mut content = String::new(); for key in keys { content.push_str(format!("{} {} {}\n", key.kind, key.data, key.user).as_str()) } let _one_at_a_time = self.update_lock.acquire().await; let tmp = path.with_extension("new"); fs::write(&tmp, content.as_bytes()).await?; if let Ok(metadata) = fs::metadata(path).await { // Try to replicate ownership and permissions of original file fs::set_permissions(&tmp, metadata.permissions()) .await .unwrap_or(()); std::os::unix::fs::chown(&tmp, Some(metadata.uid()), Some(metadata.gid())) .unwrap_or(()); } fs::rename(tmp, path).await?; info!("Updated {path:?}, {} keys", keys.len()); Ok(()) } } async fn setup_users_keys( authorized_keys: &AuthorizedKeys, config: &Config<'_>, db: &Db, ) -> anyhow::Result<()> { let keys = sqlx::query!("SELECT id,user,kind,data FROM user_keys ORDER BY id") .fetch(&**db) .map_ok(|r| Key { id: r.id, user: r.user, kind: r.kind, data: r.data, }) .try_collect::>() .await .unwrap(); let path = PathBuf::from(config.authorized_keys.to_string()); { let mut data = authorized_keys.data.lock().unwrap(); data.keys = keys.clone(); } authorized_keys.update(path.as_path(), &keys).await?; Ok(()) } async fn setup_users(rocket: Rocket) -> fairing::Result { match rocket.state::() { Some(config) => match rocket.state::() { Some(roots) => match Db::fetch(&rocket) { Some(db) => match setup_users_keys(roots, config, db).await { Ok(_) => Ok(rocket), Err(e) => { error!("{e:?}"); Err(rocket) } }, None => Err(rocket), }, None => Err(rocket), }, None => Err(rocket), } } pub fn stage() -> AdHoc { AdHoc::on_ignite("Authorized Keys Stage", |rocket| async { rocket .manage(AuthorizedKeys { data: Mutex::new(AuthorizedKeysData { keys: Vec::new() }), update_lock: Semaphore::new(1), }) .attach(AdHoc::config::()) .attach(AdHoc::try_on_ignite("Users setup", setup_users)) }) }