Skip to content

Commit a7f70cc

Browse files
committed
feat(common): serialize capabilities in AuthToken as a string with ',' separator
1 parent f6f356f commit a7f70cc

File tree

2 files changed

+78
-20
lines changed

2 files changed

+78
-20
lines changed

pubky-common/src/auth.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::sync::{Arc, Mutex};
55
use serde::{Deserialize, Serialize};
66

77
use crate::{
8-
capabilities::Capability,
8+
capabilities::{Capabilities, Capability},
99
crypto::{Keypair, PublicKey, Signature},
1010
namespaces::PUBKY_AUTH,
1111
timestamp::Timestamp,
@@ -37,11 +37,11 @@ pub struct AuthToken {
3737
/// The [PublicKey] of the owner of the resources being accessed by this token.
3838
pubky: PublicKey,
3939
// Variable length capabilities
40-
capabilities: Vec<Capability>,
40+
capabilities: Capabilities,
4141
}
4242

4343
impl AuthToken {
44-
pub fn sign(keypair: &Keypair, capabilities: Vec<Capability>) -> Self {
44+
pub fn sign(keypair: &Keypair, capabilities: impl Into<Capabilities>) -> Self {
4545
let timestamp = Timestamp::now();
4646

4747
let mut token = Self {
@@ -50,7 +50,7 @@ impl AuthToken {
5050
version: 0,
5151
timestamp,
5252
pubky: keypair.public_key(),
53-
capabilities,
53+
capabilities: capabilities.into(),
5454
};
5555

5656
let serialized = token.serialize();
@@ -61,7 +61,7 @@ impl AuthToken {
6161
}
6262

6363
pub fn capabilities(&self) -> &[Capability] {
64-
&self.capabilities
64+
&self.capabilities.0
6565
}
6666

6767
fn verify(bytes: &[u8]) -> Result<Self, Error> {
@@ -225,13 +225,13 @@ mod tests {
225225

226226
verifier.verify(serialized).unwrap();
227227

228-
assert_eq!(token.capabilities, capabilities);
228+
assert_eq!(token.capabilities, capabilities.into());
229229
}
230230

231231
#[test]
232232
fn expired() {
233233
let signer = Keypair::random();
234-
let capabilities = vec![Capability::root()];
234+
let capabilities = Capabilities(vec![Capability::root()]);
235235

236236
let verifier = AuthVerifier::default();
237237

@@ -272,7 +272,7 @@ mod tests {
272272

273273
verifier.verify(serialized).unwrap();
274274

275-
assert_eq!(token.capabilities, capabilities);
275+
assert_eq!(token.capabilities, capabilities.into());
276276

277277
assert_eq!(verifier.verify(serialized), Err(Error::AlreadyUsed));
278278
}

pubky-common/src/capabilities.rs

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,6 @@ impl TryFrom<char> for Ability {
5050
}
5151
}
5252

53-
impl TryFrom<String> for Capability {
54-
type Error = Error;
55-
56-
fn try_from(value: String) -> Result<Self, Error> {
57-
value.as_str().try_into()
58-
}
59-
}
60-
6153
impl Display for Capability {
6254
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6355
write!(
@@ -69,6 +61,14 @@ impl Display for Capability {
6961
}
7062
}
7163

64+
impl TryFrom<String> for Capability {
65+
type Error = Error;
66+
67+
fn try_from(value: String) -> Result<Self, Error> {
68+
value.as_str().try_into()
69+
}
70+
}
71+
7272
impl TryFrom<&str> for Capability {
7373
type Error = Error;
7474

@@ -105,6 +105,28 @@ impl TryFrom<&str> for Capability {
105105
}
106106
}
107107

108+
impl Serialize for Capability {
109+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110+
where
111+
S: serde::Serializer,
112+
{
113+
let string = self.to_string();
114+
115+
string.serialize(serializer)
116+
}
117+
}
118+
119+
impl<'de> Deserialize<'de> for Capability {
120+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
121+
where
122+
D: serde::Deserializer<'de>,
123+
{
124+
let string: String = Deserialize::deserialize(deserializer)?;
125+
126+
string.try_into().map_err(serde::de::Error::custom)
127+
}
128+
}
129+
108130
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
109131
pub enum Error {
110132
#[error("Capability: Invalid resource path: does not start with `/`")]
@@ -115,25 +137,61 @@ pub enum Error {
115137
InvalidAbility,
116138
}
117139

118-
impl Serialize for Capability {
140+
#[derive(Clone, Default, Debug, PartialEq, Eq)]
141+
/// A wrapper around `Vec<Capability>` to enable serialization without
142+
/// a varint. Useful when [Capabilities] are at the end of a struct.
143+
pub struct Capabilities(pub Vec<Capability>);
144+
145+
impl Capabilities {
146+
pub fn contains(&self, capability: &Capability) -> bool {
147+
self.0.contains(capability)
148+
}
149+
}
150+
151+
impl From<Vec<Capability>> for Capabilities {
152+
fn from(value: Vec<Capability>) -> Self {
153+
Self(value)
154+
}
155+
}
156+
157+
impl From<Capabilities> for Vec<Capability> {
158+
fn from(value: Capabilities) -> Self {
159+
value.0
160+
}
161+
}
162+
163+
impl Serialize for Capabilities {
119164
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
120165
where
121166
S: serde::Serializer,
122167
{
123-
let string = self.to_string();
168+
let string = self
169+
.0
170+
.iter()
171+
.map(|c| c.to_string())
172+
.collect::<Vec<_>>()
173+
.join(",");
124174

125175
string.serialize(serializer)
126176
}
127177
}
128178

129-
impl<'de> Deserialize<'de> for Capability {
179+
impl<'de> Deserialize<'de> for Capabilities {
130180
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
131181
where
132182
D: serde::Deserializer<'de>,
133183
{
134184
let string: String = Deserialize::deserialize(deserializer)?;
135185

136-
string.try_into().map_err(serde::de::Error::custom)
186+
let mut caps = vec![];
187+
188+
for s in string.split(',') {
189+
if let Ok(cap) = Capability::try_from(s) {
190+
caps.push(cap);
191+
};
192+
}
193+
194+
Ok(Capabilities(caps))
137195
}
138196
}
139197

0 commit comments

Comments
 (0)