Skip to content

Commit

Permalink
feat: add internal did:web resolution
Browse files Browse the repository at this point in the history
Remove dependency on the Spruce crates for did:web resolution

Resolves #42

Adds a test related to #39, but needs a trusted host for a real-resolution
test for a path-based DID.
  • Loading branch information
enmand committed May 20, 2024
1 parent 6a54d27 commit 6a580a0
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 15 deletions.
3 changes: 2 additions & 1 deletion crates/dids/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ did-web = "0.2.2"
jwk = { path = "../jwk" }
keys = { path = "../keys" }
regex = "1.10.4"
reqwest = { version = "0.12.4", features = ["json"] }
serde = { workspace = true }
serde_json = { workspace = true }
ssi-core = "0.1.0"
Expand All @@ -23,4 +24,4 @@ thiserror = { workspace = true }

[dev-dependencies]
chrono = { workspace = true }
tokio = { version = "1.34.0", features = ["macros", "test-util"] }
tokio = { version = "1.34.0", features = ["macros", "test-util"] }
2 changes: 1 addition & 1 deletion crates/dids/src/identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub enum IdentifierError {
ParseFailure(String),
}

#[derive(Debug, Default, PartialEq)]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Identifier {
// URI represents the complete Decentralized Identifier (DID) URI.
// Spec: https://www.w3.org/TR/did-core/#did-syntax
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::methods::{Method, ResolutionResult, Resolve};
use did_web::DIDWeb as SpruceDidWebMethod;
use ssi_dids::did_resolve::{DIDResolver, ResolutionInputMetadata};
mod resolver;

use crate::{
identifier::Identifier,
methods::{Method, ResolutionResult, Resolve},
resolver::ResolutionError,
};
use resolver::Resolver;

/// Concrete implementation for a did:web DID
pub struct DidWeb {}
Expand All @@ -11,16 +16,13 @@ impl Method for DidWeb {

impl Resolve for DidWeb {
async fn resolve(did_uri: &str) -> ResolutionResult {
let input_metadata = ResolutionInputMetadata::default();
let (spruce_resolution_metadata, spruce_document, spruce_document_metadata) =
SpruceDidWebMethod.resolve(did_uri, &input_metadata).await;

match ResolutionResult::from_spruce(
spruce_resolution_metadata,
spruce_document,
spruce_document_metadata,
) {
Ok(r) => r,
let identifier = match Identifier::parse(did_uri) {
Ok(identifier) => identifier,
Err(_) => return ResolutionResult::from_error(ResolutionError::InvalidDid),
};

match Resolver::new(identifier).await {
Ok(result) => result,
Err(e) => ResolutionResult::from_error(e),
}
}
Expand Down
99 changes: 99 additions & 0 deletions crates/dids/src/methods/web/resolver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use std::{
future::{Future, IntoFuture},
pin::Pin,
};

use reqwest::header::{HeaderMap, HeaderValue};

use crate::{
document::Document,
identifier::Identifier,
resolver::{ResolutionError, ResolutionResult},
};

// PORT_SEP is the : character that separates the domain from the port in a URI.
const PORT_SEP: &str = "%3A";

const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"));

/// Resolver is the implementation of the did:web method for resolcing DID URIs. It is responsible
/// for fetching the DID Document from the web according for the did-web spec.
pub struct Resolver {
did_url: String,
}

impl Resolver {
pub fn new(did_uri: Identifier) -> Self {
// note: delimited is : generally, but ; is allowed by the spec. The did-web spec (§3.2) says
// ; should be avoided because of it's potential use for matrix URIs.
let did_url = match did_uri.id.split_once(':') {
Some((domain, path)) => format!(
"{}/{}",
domain.replace(PORT_SEP, ":"),
path.split(':').collect::<Vec<&str>>().join("/"),
),
None => format!("{}/{}", did_uri.id.replace(PORT_SEP, ":"), ".well-known",),
};

Self {
did_url: format!("https://{}/did.json", did_url),
}
}
}

// This trait implements the actual logic for resolving a DID URI to a DID Document.
impl IntoFuture for Resolver {
type Output = Result<ResolutionResult, ResolutionError>;
type IntoFuture = Pin<Box<dyn Future<Output = Self::Output> + Send + Sync>>;

fn into_future(self) -> Self::IntoFuture {
let mut headers = HeaderMap::new();
headers.append(
reqwest::header::USER_AGENT,
HeaderValue::from_static(USER_AGENT),
);

Box::pin(async move {
let client = reqwest::Client::builder()
.default_headers(headers)
.build()
.map_err(|_| ResolutionError::InternalError)?;

let response = client
.get(&self.did_url)
.send()
.await
.map_err(|_| ResolutionError::InternalError)?;

if response.status().is_success() {
let did_document = response
.json::<Document>()
.await
.map_err(|_| ResolutionError::RepresentationNotSupported)?;

Ok(ResolutionResult {
did_document: Some(did_document),
..Default::default()
})
} else {
Err(ResolutionError::NotFound)
}
})
}
}

#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn resolution_success() {
let did_uri = "did:web:tbd.website";
let result = Resolver::new(Identifier::parse(did_uri).unwrap());
assert_eq!(result.did_url, "https://tbd.website/.well-known/did.json");

let did_uri = "did:web:tbd.website:with:path";
let result = Resolver::new(Identifier::parse(did_uri).unwrap());
assert_eq!(result.did_url, "https://tbd.website/with/path/did.json");
}
}

0 comments on commit 6a580a0

Please sign in to comment.