-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add internal did:web resolution #190
Changes from all commits
0e64772
8177110
777bab2
633df6b
69ccca3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
use std::{ | ||
future::{Future, IntoFuture}, | ||
pin::Pin, | ||
}; | ||
|
||
use reqwest::header::{HeaderMap, HeaderValue}; | ||
|
||
use crate::dids::{ | ||
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")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is interesting, @enmand can you help me understand your reasoning for including setting the Also I wonder, do you know how this would impact browser environments given we were to run this in a browser via a WASM binding? It's not a huge deal in this moment because we're deprioritizing WASM in this moment, so no need to spend a bunch of time investigating, but again my skepticism would lead me to conclude we're better off leaving this out until we have clarity on the broad array of cross-platform implications. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mostly because it's a way for the did:web server to understand what versions/libraries it's dealing with, though given it's not a service-to-service environment and there are many callers maybe it's not as important. I'm not strong on keeping it or removing it -- I only added it because it was a natural thing for me to include, but also happy to not have it included. Re. WASM environments -- that's a good questions. I'll implement it in a browser/WASM environment soon for the DWN project I'm working on, and if this is included I can report back (otherwise, I'll probably have other places where I make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm wrangling around code into the APID in this PR. I'm going to go ahead and merge this code and copy everything over into the temporary |
||
|
||
/// Resolver is the implementation of the did:web method for resolving 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), | ||
} | ||
} | ||
|
||
async fn resolve(url: String) -> Result<ResolutionResult, ResolutionError> { | ||
let mut headers = HeaderMap::new(); | ||
headers.append( | ||
reqwest::header::USER_AGENT, | ||
HeaderValue::from_static(USER_AGENT), | ||
); | ||
|
||
let client = reqwest::Client::builder() | ||
.default_headers(headers) | ||
.build() | ||
.map_err(|_| ResolutionError::InternalError)?; | ||
|
||
let response = client | ||
.get(&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) | ||
} | ||
} | ||
} | ||
|
||
// 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>>>; | ||
|
||
fn into_future(self) -> Self::IntoFuture { | ||
let did_url = self.did_url; | ||
Box::pin(async move { Self::resolve(did_url).await }) | ||
} | ||
} | ||
|
||
#[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"); | ||
|
||
let did_uri = "did:web:tbd.website%3A8080"; | ||
let result = Resolver::new(Identifier::parse(did_uri).unwrap()); | ||
assert_eq!( | ||
result.did_url, | ||
"https://tbd.website:8080/.well-known/did.json" | ||
); | ||
|
||
let did_uri = "did:web:tbd.website%3A8080:with:path"; | ||
let result = Resolver::new(Identifier::parse(did_uri).unwrap()); | ||
assert_eq!( | ||
result.did_url, | ||
"https://tbd.website:8080/with/path/did.json" | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clever. This may have implications but I'm fine with moving forward with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it because the example
did:web
(https://tbd.website/.well-known/did.json) was failing to Deserialize, complaining aboutverification_methods
being missing -- though maybe there's another way (aside from #233)?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should update that DID because it's not really adding any value. I'm not sure how it can be used in its current state.