From a7f70ccb1b430807663da2c17ee32589c9e378ce Mon Sep 17 00:00:00 2001 From: nazeh Date: Thu, 5 Sep 2024 13:06:00 +0300 Subject: [PATCH] feat(common): serialize capabilities in AuthToken as a string with ',' separator --- pubky-common/src/auth.rs | 16 +++--- pubky-common/src/capabilities.rs | 94 ++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 26 deletions(-) diff --git a/pubky-common/src/auth.rs b/pubky-common/src/auth.rs index 2329c99..be19cef 100644 --- a/pubky-common/src/auth.rs +++ b/pubky-common/src/auth.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex}; use serde::{Deserialize, Serialize}; use crate::{ - capabilities::Capability, + capabilities::{Capabilities, Capability}, crypto::{Keypair, PublicKey, Signature}, namespaces::PUBKY_AUTH, timestamp::Timestamp, @@ -37,11 +37,11 @@ pub struct AuthToken { /// The [PublicKey] of the owner of the resources being accessed by this token. pubky: PublicKey, // Variable length capabilities - capabilities: Vec, + capabilities: Capabilities, } impl AuthToken { - pub fn sign(keypair: &Keypair, capabilities: Vec) -> Self { + pub fn sign(keypair: &Keypair, capabilities: impl Into) -> Self { let timestamp = Timestamp::now(); let mut token = Self { @@ -50,7 +50,7 @@ impl AuthToken { version: 0, timestamp, pubky: keypair.public_key(), - capabilities, + capabilities: capabilities.into(), }; let serialized = token.serialize(); @@ -61,7 +61,7 @@ impl AuthToken { } pub fn capabilities(&self) -> &[Capability] { - &self.capabilities + &self.capabilities.0 } fn verify(bytes: &[u8]) -> Result { @@ -225,13 +225,13 @@ mod tests { verifier.verify(serialized).unwrap(); - assert_eq!(token.capabilities, capabilities); + assert_eq!(token.capabilities, capabilities.into()); } #[test] fn expired() { let signer = Keypair::random(); - let capabilities = vec![Capability::root()]; + let capabilities = Capabilities(vec![Capability::root()]); let verifier = AuthVerifier::default(); @@ -272,7 +272,7 @@ mod tests { verifier.verify(serialized).unwrap(); - assert_eq!(token.capabilities, capabilities); + assert_eq!(token.capabilities, capabilities.into()); assert_eq!(verifier.verify(serialized), Err(Error::AlreadyUsed)); } diff --git a/pubky-common/src/capabilities.rs b/pubky-common/src/capabilities.rs index ece2a3f..2cb28fc 100644 --- a/pubky-common/src/capabilities.rs +++ b/pubky-common/src/capabilities.rs @@ -50,14 +50,6 @@ impl TryFrom for Ability { } } -impl TryFrom for Capability { - type Error = Error; - - fn try_from(value: String) -> Result { - value.as_str().try_into() - } -} - impl Display for Capability { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -69,6 +61,14 @@ impl Display for Capability { } } +impl TryFrom for Capability { + type Error = Error; + + fn try_from(value: String) -> Result { + value.as_str().try_into() + } +} + impl TryFrom<&str> for Capability { type Error = Error; @@ -105,16 +105,6 @@ impl TryFrom<&str> for Capability { } } -#[derive(thiserror::Error, Debug, PartialEq, Eq)] -pub enum Error { - #[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 { fn serialize(&self, serializer: S) -> Result where @@ -137,6 +127,74 @@ impl<'de> Deserialize<'de> for Capability { } } +#[derive(thiserror::Error, Debug, PartialEq, Eq)] +pub enum Error { + #[error("Capability: Invalid resource path: does not start with `/`")] + InvalidResource, + #[error("Capability: Invalid format should be :")] + InvalidFormat, + #[error("Capability: Invalid Ability")] + InvalidAbility, +} + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +/// A wrapper around `Vec` to enable serialization without +/// a varint. Useful when [Capabilities] are at the end of a struct. +pub struct Capabilities(pub Vec); + +impl Capabilities { + pub fn contains(&self, capability: &Capability) -> bool { + self.0.contains(capability) + } +} + +impl From> for Capabilities { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From for Vec { + fn from(value: Capabilities) -> Self { + value.0 + } +} + +impl Serialize for Capabilities { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let string = self + .0 + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(","); + + string.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Capabilities { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let string: String = Deserialize::deserialize(deserializer)?; + + let mut caps = vec![]; + + for s in string.split(',') { + if let Ok(cap) = Capability::try_from(s) { + caps.push(cap); + }; + } + + Ok(Capabilities(caps)) + } +} + #[cfg(test)] mod tests { use super::*;