feat(common): serialize capabilities in AuthToken as a string with ',' separator

This commit is contained in:
nazeh
2024-09-05 13:06:00 +03:00
parent f6f356f6ae
commit a7f70ccb1b
2 changed files with 84 additions and 26 deletions

View File

@@ -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<Capability>,
capabilities: Capabilities,
}
impl AuthToken {
pub fn sign(keypair: &Keypair, capabilities: Vec<Capability>) -> Self {
pub fn sign(keypair: &Keypair, capabilities: impl Into<Capabilities>) -> 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<Self, Error> {
@@ -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));
}

View File

@@ -50,14 +50,6 @@ impl TryFrom<char> for Ability {
}
}
impl TryFrom<String> for Capability {
type Error = Error;
fn try_from(value: String) -> Result<Self, Error> {
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<String> for Capability {
type Error = Error;
fn try_from(value: String) -> Result<Self, Error> {
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 <resource>:<abilities>")]
InvalidFormat,
#[error("Capability: Invalid Ability")]
InvalidAbility,
}
impl Serialize for Capability {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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 <resource>:<abilities>")]
InvalidFormat,
#[error("Capability: Invalid Ability")]
InvalidAbility,
}
#[derive(Clone, Default, Debug, PartialEq, Eq)]
/// A wrapper around `Vec<Capability>` to enable serialization without
/// a varint. Useful when [Capabilities] are at the end of a struct.
pub struct Capabilities(pub Vec<Capability>);
impl Capabilities {
pub fn contains(&self, capability: &Capability) -> bool {
self.0.contains(capability)
}
}
impl From<Vec<Capability>> for Capabilities {
fn from(value: Vec<Capability>) -> Self {
Self(value)
}
}
impl From<Capabilities> for Vec<Capability> {
fn from(value: Capabilities) -> Self {
value.0
}
}
impl Serialize for Capabilities {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let string = self
.0
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(",");
string.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Capabilities {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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::*;