use activitystreams::base::AnyBase; use sled::{Db, Tree}; use std::{fmt, sync::Arc}; use url::Url; use uuid::Uuid; mod actions; mod keys; pub(crate) use actions::ingest; use keys::ExtendedPerson; pub trait ApubIds { fn gen_id(&self) -> Url; fn public_key(&self, id: &Url) -> Option; fn following(&self, id: &Url) -> Option; fn followers(&self, id: &Url) -> Option; fn inbox(&self, id: &Url) -> Option; fn outbox(&self, id: &Url) -> Option; fn shared_inbox(&self) -> Url; } #[derive(Debug, thiserror::Error)] pub enum StoreError { #[error("{0}")] Sled(#[from] sled::Error), #[error("{0}")] Transaction(#[from] sled::transaction::TransactionError), #[error("{0}")] Json(#[from] serde_json::Error), #[error("Error generating keys")] Key, } #[derive(Clone)] pub struct Store { apub_id: Tree, id_apub: Tree, deleted: Tree, objects: Tree, seen: Tree, keys: Tree, profile_key: Tree, endpoints: Tree, info: Arc, } #[derive(Clone, Debug)] pub enum StoredRecords { Profile(Uuid), Submission(Uuid), Comment(Uuid), React(Uuid), File(Uuid), Block(Uuid), Follow(Uuid), FollowRequest(Uuid), } fn url_from_ivec(ivec: sled::IVec) -> Option { String::from_utf8_lossy(&ivec).parse::().ok() } fn record_from_ivec(ivec: sled::IVec) -> Option { StoredRecords::from_slice(&ivec).ok() } impl StoredRecords { fn from_slice(slice: &[u8]) -> Result { String::from_utf8_lossy(slice).parse::() } pub fn profile(&self) -> Option { match self { Self::Profile(id) => Some(*id), _ => None, } } pub fn submission(&self) -> Option { match self { Self::Submission(id) => Some(*id), _ => None, } } pub fn comment(&self) -> Option { match self { Self::Comment(id) => Some(*id), _ => None, } } pub fn react(&self) -> Option { match self { Self::React(id) => Some(*id), _ => None, } } pub fn file(&self) -> Option { match self { Self::File(id) => Some(*id), _ => None, } } pub fn block(&self) -> Option { match self { Self::Block(id) => Some(*id), _ => None, } } pub fn follow(&self) -> Option { match self { Self::Follow(id) => Some(*id), _ => None, } } pub fn follow_request(&self) -> Option { match self { Self::FollowRequest(id) => Some(*id), _ => None, } } } #[derive(Debug, thiserror::Error)] #[error("Error generating key")] struct KeyError; #[derive(serde::Deserialize, serde::Serialize)] struct Keys { public: String, private: Option, } #[derive(serde::Deserialize, serde::Serialize)] struct Endpoints { inbox: Url, outbox: Url, following: Url, followers: Url, shared_inbox: Url, public_key: Url, } impl Store { pub(crate) fn build( ids: impl ApubIds + Send + Sync + 'static, db: &Db, ) -> Result { Ok(Store { id_apub: db.open_tree("/profiles/apub/id-apub")?, apub_id: db.open_tree("/profiles/apub/apub-id")?, deleted: db.open_tree("/profiles/apub/deleted")?, objects: db.open_tree("/profiles/apub/objects")?, seen: db.open_tree("/profiles/apub/seen")?, keys: db.open_tree("/profiles/apub/keys")?, profile_key: db.open_tree("/profiles/apub/profile/key")?, endpoints: db.open_tree("/profiles/apub/endpoints")?, info: Arc::new(ids), }) } pub(crate) fn key_for_profile(&self, profile_id: Uuid) -> Result, StoreError> { self.profile_key .get(profile_key_id(profile_id)) .map(|opt| opt.and_then(url_from_ivec)) .map_err(StoreError::from) } pub(crate) fn public_key_for_id(&self, key_id: &Url) -> Result, StoreError> { if let Some(ivec) = self.keys.get(key_id.as_str())? { let keys: Keys = serde_json::from_slice(&ivec)?; return Ok(Some(keys.public)); } Ok(None) } pub(crate) fn store_public_key( &self, profile_id: Uuid, key_id: &Url, public_key: &str, ) -> Result<(), StoreError> { let keys = Keys::from_public(public_key)?; let keys_vec = serde_json::to_vec(&keys)?; self.keys.insert(key_id.as_str(), keys_vec)?; self.profile_key .insert(profile_key_id(profile_id), key_id.as_str())?; Ok(()) } pub(crate) fn object(&self, id: &Url) -> Result, StoreError> { if let Some(ivec) = self.objects.get(id.as_str())? { let any_base: AnyBase = serde_json::from_slice(&ivec)?; return Ok(Some(any_base)); } Ok(None) } pub(crate) fn store_object(&self, any_base: AnyBase) -> Result<(), StoreError> { if let Some(id) = any_base.id() { let obj_vec = serde_json::to_vec(&any_base)?; self.objects.insert(id.as_str(), obj_vec)?; } Ok(()) } pub(crate) fn is_seen(&self, url: &Url) -> Result { Ok(self.seen.get(url.as_str())?.is_some()) } pub(crate) fn seen(&self, url: &Url) -> Result<(), StoreError> { self.seen.insert(url.as_str(), url.as_str())?; Ok(()) } pub(crate) fn delete_id(&self, id: StoredRecords) -> Result<(), StoreError> { if let Some(url_ivec) = self.id_apub.remove(id.to_string())? { self.apub_id.remove(url_ivec)?; } Ok(()) } pub(crate) fn delete_apub(&self, apub_id: &Url) -> Result<(), StoreError> { if let Some(record_ivec) = self.apub_id.remove(apub_id.as_str())? { self.id_apub.remove(record_ivec)?; } Ok(()) } pub(crate) fn submission(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::Submission(id)) } pub(crate) fn profile(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::Profile(id)) } pub(crate) fn comment(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::Comment(id)) } pub(crate) fn react(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::React(id)) } pub(crate) fn follow(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::Follow(id)) } pub(crate) fn follow_request(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::FollowRequest(id)) } pub(crate) fn block(&self, apub_id: &Url, id: Uuid) -> Result<(), StoreError> { self.establish(apub_id, StoredRecords::Block(id)) } fn establish(&self, apub_id: &Url, id: StoredRecords) -> Result<(), StoreError> { self.apub_id .insert(apub_id.as_str(), id.to_string().as_bytes())?; self.id_apub .insert(id.to_string(), apub_id.as_str().as_bytes())?; Ok(()) } pub(crate) fn id_for_apub(&self, apub_id: &Url) -> Result, StoreError> { Ok(self .apub_id .get(apub_id.as_str())? .and_then(record_from_ivec)) } pub(crate) fn apub_for_id(&self, id: StoredRecords) -> Result, StoreError> { Ok(self.id_apub.get(id.to_string())?.and_then(url_from_ivec)) } } impl Keys { fn from_public(public: &str) -> Result { use rsa::RSAPublicKey; use rsa_pem::KeyExt; RSAPublicKey::from_pem_pkcs8(public).map_err(|_| KeyError)?; Ok(Keys { public: public.to_owned(), private: None, }) } fn generate() -> Result { use rsa::RSAPrivateKey; use rsa_pem::KeyExt; let mut rng = rand::thread_rng(); let private_key = RSAPrivateKey::new(&mut rng, 2048).map_err(|_| KeyError)?; let private = private_key.to_pem_pkcs8().map_err(|_| KeyError)?; let public_key = private_key.to_public_key(); let public = public_key.to_pem_pkcs8().map_err(|_| KeyError)?; Ok(Keys { public, private: Some(private), }) } } fn profile_key_id(profile_id: Uuid) -> String { format!("/profile/{}/key-id", profile_id) } fn record_endpoints_key(record: StoredRecords) -> String { format!("/object/{}/endpoints", record) } fn record_keys_key(record: StoredRecords) -> String { format!("/object/{}/keys", record) } fn object_record_key(object_id: &Url) -> String { format!("/object/{}/record", object_id) } fn owner_record_key(owner_id: &Url) -> String { format!("/owner/{}/record", owner_id) } fn record_object_key(record: StoredRecords) -> String { format!("/record/{}/object", record) } impl fmt::Debug for Store { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Store") .field("relationship", &"Tree") .field("keys", &"Tree") .field("endpoints", &"Tree") .field("info", &"Rc") .finish() } } #[derive(Debug, thiserror::Error)] #[error("Failed to parse StoredRecord")] pub struct StoredRecordsError; impl std::str::FromStr for StoredRecords { type Err = StoredRecordsError; fn from_str(s: &str) -> Result { let split_pos = s.find(':').ok_or(StoredRecordsError)?; let (kind, uuid) = s.split_at(split_pos); let uuid = uuid.trim_start_matches(':'); let id = uuid.parse::().map_err(|_| StoredRecordsError)?; let record = match kind { "profile" => StoredRecords::Profile(id), "submission" => StoredRecords::Submission(id), "comment" => StoredRecords::Comment(id), "react" => StoredRecords::React(id), "file" => StoredRecords::File(id), "block" => StoredRecords::Block(id), "follow" => StoredRecords::Follow(id), "follow-request" => StoredRecords::FollowRequest(id), _ => return Err(StoredRecordsError), }; Ok(record) } } impl fmt::Display for StoredRecords { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { StoredRecords::Profile(id) => write!(f, "profile:{}", id), StoredRecords::Submission(id) => write!(f, "submission:{}", id), StoredRecords::Comment(id) => write!(f, "comment:{}", id), StoredRecords::React(id) => write!(f, "react:{}", id), StoredRecords::File(id) => write!(f, "file:{}", id), StoredRecords::Block(id) => write!(f, "block:{}", id), StoredRecords::Follow(id) => write!(f, "follow:{}", id), StoredRecords::FollowRequest(id) => write!(f, "follow-request:{}", id), } } } impl From for StoreError { fn from(_: KeyError) -> Self { StoreError::Key } }