diff --git a/pubky-common/src/auth.rs b/pubky-common/src/auth.rs index 71e69a5..68fafb4 100644 --- a/pubky-common/src/auth.rs +++ b/pubky-common/src/auth.rs @@ -207,7 +207,7 @@ mod tests { fn v0_id_signable() { let signer = Keypair::random(); let audience = Keypair::random().public_key(); - let capabilities = vec![Capability::pubky_root()]; + let capabilities = vec![Capability::root()]; let token = AuthToken::sign(&signer, &audience, capabilities.clone()); @@ -228,7 +228,7 @@ mod tests { fn sign_verify() { let signer = Keypair::random(); let audience = Keypair::random().public_key(); - let capabilities = vec![Capability::pubky_root()]; + let capabilities = vec![Capability::root()]; let verifier = AuthVerifier::new(audience.clone()); @@ -245,7 +245,7 @@ mod tests { fn expired() { let signer = Keypair::random(); let audience = Keypair::random().public_key(); - let capabilities = vec![Capability::pubky_root()]; + let capabilities = vec![Capability::root()]; let verifier = AuthVerifier::new(audience.clone()); @@ -278,7 +278,7 @@ mod tests { fn already_used() { let signer = Keypair::random(); let audience = Keypair::random().public_key(); - let capabilities = vec![Capability::pubky_root()]; + let capabilities = vec![Capability::root()]; let verifier = AuthVerifier::new(audience.clone()); diff --git a/pubky-common/src/capabilities.rs b/pubky-common/src/capabilities.rs index 2a46cde..ece2a3f 100644 --- a/pubky-common/src/capabilities.rs +++ b/pubky-common/src/capabilities.rs @@ -2,56 +2,50 @@ use std::fmt::Display; use serde::{Deserialize, Serialize}; -const PUBKY_CAP_PREFIX: &str = "pk!"; - #[derive(Debug, Clone, PartialEq, Eq)] -pub enum Capability { - /// Pubky Homeserver's capabilities - Pubky(PubkyCap), - Unknown(String), +pub struct Capability { + pub resource: String, + pub abilities: Vec, } impl Capability { - /// Create a [PubkyCap] at the root path `/` with all the available [PubkyAbility] - pub fn pubky_root() -> Self { - Capability::Pubky(PubkyCap { - path: "/".to_string(), - abilities: vec![PubkyAbility::Read, PubkyAbility::Write], - }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PubkyCap { - pub path: String, - pub abilities: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PubkyAbility { - /// Can read the resource at the specified path (GET requests). - Read, - /// Can write to the resource at the specified path (PUT/POST/DELETE requests). - Write, -} - -impl From<&PubkyAbility> for char { - fn from(value: &PubkyAbility) -> Self { - match value { - PubkyAbility::Read => 'r', - PubkyAbility::Write => 'w', + /// Create a root [Capability] at the `/` path with all the available [PubkyAbility] + pub fn root() -> Self { + Capability { + resource: "/".to_string(), + abilities: vec![Ability::Read, Ability::Write], } } } -impl TryFrom for PubkyAbility { +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Ability { + /// Can read the resource at the specified path (GET requests). + Read, + /// Can write to the resource at the specified path (PUT/POST/DELETE requests). + Write, + /// Unknown ability + Unknown(char), +} + +impl From<&Ability> for char { + fn from(value: &Ability) -> Self { + match value { + Ability::Read => 'r', + Ability::Write => 'w', + Ability::Unknown(char) => char.to_owned(), + } + } +} + +impl TryFrom for Ability { type Error = Error; fn try_from(value: char) -> Result { match value { 'r' => Ok(Self::Read), 'w' => Ok(Self::Write), - _ => Err(Error::InvalidPubkyAbility), + _ => Err(Error::InvalidAbility), } } } @@ -66,16 +60,12 @@ impl TryFrom for Capability { impl Display for Capability { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Pubky(cap) => write!( - f, - "{}{}:{}", - PUBKY_CAP_PREFIX, - cap.path, - cap.abilities.iter().map(char::from).collect::() - ), - Self::Unknown(string) => write!(f, "{string}"), - } + write!( + f, + "{}:{}", + self.resource, + self.abilities.iter().map(char::from).collect::() + ) } } @@ -83,48 +73,46 @@ impl TryFrom<&str> for Capability { type Error = Error; fn try_from(value: &str) -> Result { - if value.starts_with(PUBKY_CAP_PREFIX) { - let mut rsplit = value.rsplit(':'); - - let mut abilities = Vec::new(); - - for char in rsplit - .next() - .ok_or(Error::MissingField("abilities"))? - .chars() - { - let ability = PubkyAbility::try_from(char)?; - - match abilities.binary_search_by(|element| char::from(element).cmp(&char)) { - Ok(_) => {} - Err(index) => { - abilities.insert(index, ability); - } - } - } - - let path = rsplit.next().ok_or(Error::MissingField("path"))?[PUBKY_CAP_PREFIX.len()..] - .to_string(); - - if !path.starts_with('/') { - return Err(Error::InvalidPath); - } - - return Ok(Capability::Pubky(PubkyCap { path, abilities })); + if value.matches(':').count() != 1 { + return Err(Error::InvalidFormat); } - Ok(Capability::Unknown(value.to_string())) + if !value.starts_with('/') { + return Err(Error::InvalidResource); + } + + let abilities_str = value.rsplit(':').next().unwrap_or(""); + + let mut abilities = Vec::new(); + + for char in abilities_str.chars() { + let ability = Ability::try_from(char)?; + + match abilities.binary_search_by(|element| char::from(element).cmp(&char)) { + Ok(_) => {} + Err(index) => { + abilities.insert(index, ability); + } + } + } + + let resource = value[0..value.len() - abilities_str.len() - 1].to_string(); + + Ok(Capability { + resource, + abilities, + }) } } #[derive(thiserror::Error, Debug, PartialEq, Eq)] pub enum Error { - #[error("PubkyCap: Missing field {0}")] - MissingField(&'static str), - #[error("PubkyCap: InvalidPath does not start with `/`")] - InvalidPath, - #[error("Invalid PubkyAbility")] - InvalidPubkyAbility, + #[error("Capability: Invalid resource path: does not start with `/`")] + InvalidResource, + #[error("Capability: Invalid format should be :")] + InvalidFormat, + #[error("Capability: Invalid Ability")] + InvalidAbility, } impl Serialize for Capability { @@ -155,13 +143,13 @@ mod tests { #[test] fn pubky_caps() { - let cap = Capability::Pubky(PubkyCap { - path: "/pub/pubky.app/".to_string(), - abilities: vec![PubkyAbility::Read, PubkyAbility::Write], - }); + let cap = Capability { + resource: "/pub/pubky.app/".to_string(), + abilities: vec![Ability::Read, Ability::Write], + }; // Read and write withing directory `/pub/pubky.app/`. - let expected_string = "pk!/pub/pubky.app/:rw"; + let expected_string = "/pub/pubky.app/:rw"; assert_eq!(cap.to_string(), expected_string); diff --git a/pubky-common/src/session.rs b/pubky-common/src/session.rs index 83be3a7..eb8ff2b 100644 --- a/pubky-common/src/session.rs +++ b/pubky-common/src/session.rs @@ -66,6 +66,8 @@ impl Session { Ok(from_bytes(bytes)?) } + + // TODO: add `can_read()`, `can_write()` and `is_root()` methods } pub type Result = core::result::Result; @@ -86,7 +88,7 @@ mod tests { fn serialize() { let session = Session { user_agent: "foo".to_string(), - capabilities: vec![Capability::pubky_root()], + capabilities: vec![Capability::root()], ..Default::default() }; @@ -94,7 +96,7 @@ mod tests { assert_eq!( serialized, - [0, 0, 0, 3, 102, 111, 111, 1, 7, 112, 107, 33, 47, 58, 114, 119] + [0, 0, 0, 3, 102, 111, 111, 1, 4, 47, 58, 114, 119] ); let deseiralized = Session::deserialize(&serialized).unwrap(); diff --git a/pubky/src/shared/auth.rs b/pubky/src/shared/auth.rs index 6cbb65d..11b312e 100644 --- a/pubky/src/shared/auth.rs +++ b/pubky/src/shared/auth.rs @@ -26,7 +26,7 @@ impl PubkyClient { url.set_path("/signup"); - let body = AuthToken::sign(keypair, &audience, vec![Capability::pubky_root()]).serialize(); + let body = AuthToken::sign(keypair, &audience, vec![Capability::root()]).serialize(); let response = self .request(Method::POST, url.clone()) @@ -89,7 +89,7 @@ impl PubkyClient { url.set_path("/session"); - let body = AuthToken::sign(keypair, &audience, vec![Capability::pubky_root()]).serialize(); + let body = AuthToken::sign(keypair, &audience, vec![Capability::root()]).serialize(); let response = self.request(Method::POST, url).body(body).send().await?; @@ -125,7 +125,7 @@ mod tests { .unwrap() .unwrap(); - assert!(session.capabilities.contains(&Capability::pubky_root())); + assert!(session.capabilities.contains(&Capability::root())); client.signout(&keypair.public_key()).await.unwrap(); @@ -144,7 +144,7 @@ mod tests { .unwrap() .unwrap(); - assert!(session.capabilities.contains(&Capability::pubky_root())); + assert!(session.capabilities.contains(&Capability::root())); } } }