mirror of
https://github.com/aljazceru/pubky-core.git
synced 2026-01-02 13:54:32 +01:00
feat(common): simplify Capabilities
This commit is contained in:
@@ -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());
|
||||
|
||||
|
||||
@@ -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<Ability>,
|
||||
}
|
||||
|
||||
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<PubkyAbility>,
|
||||
}
|
||||
|
||||
#[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<char> 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<char> for Ability {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: char) -> Result<Self, Error> {
|
||||
match value {
|
||||
'r' => Ok(Self::Read),
|
||||
'w' => Ok(Self::Write),
|
||||
_ => Err(Error::InvalidPubkyAbility),
|
||||
_ => Err(Error::InvalidAbility),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,16 +60,12 @@ impl TryFrom<String> 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::<String>()
|
||||
),
|
||||
Self::Unknown(string) => write!(f, "{string}"),
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"{}:{}",
|
||||
self.resource,
|
||||
self.abilities.iter().map(char::from).collect::<String>()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,48 +73,46 @@ impl TryFrom<&str> for Capability {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Error> {
|
||||
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 <resource>:<abilities>")]
|
||||
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);
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ impl Session {
|
||||
|
||||
Ok(from_bytes(bytes)?)
|
||||
}
|
||||
|
||||
// TODO: add `can_read()`, `can_write()` and `is_root()` methods
|
||||
}
|
||||
|
||||
pub type Result<T> = core::result::Result<T, Error>;
|
||||
@@ -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();
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user