Skip to content
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

cli did create web #263

Merged
merged 7 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions crates/web5/src/dids/methods/did_web/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
mod resolver;

use super::{MethodError, Result};
use crate::dids::{
data_model::document::Document,
did::Did,
resolution::{
resolution_metadata::{ResolutionMetadata, ResolutionMetadataError},
resolution_result::ResolutionResult,
use crate::{
crypto::jwk::Jwk,
dids::{
data_model::{document::Document, verification_method::VerificationMethod},
did::Did,
resolution::{
resolution_metadata::{ResolutionMetadata, ResolutionMetadataError},
resolution_result::ResolutionResult,
},
},
};
use resolver::Resolver;
Expand All @@ -18,6 +21,29 @@ pub struct DidWeb {
}

impl DidWeb {
pub fn new(domain: &str, public_jwk: Jwk) -> Result<Self> {
let did = format!("did:web:{}", domain);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't quite right because the domain has to be encoded according to the spec. See here and here. Also web5-go as inspiration.

Probably we should use a strongly typed Url here to ensure the passed domain: &str is a valid domain.

I believe also did:web has a requirement of enforcing https over http, but with the caveat that, if the domain is localhost then http is allowed. We're currently setup to handle this in resolution, but here too, we should use a strongly typed Url to ensure the domain is localhost and not something like localhost-neal-example.com. Feel free to improve the resolution code as well in this PR if you think it's appropriate.


let verification_method = VerificationMethod {
id: format!("{}#key-0", did),
KendallWeihe marked this conversation as resolved.
Show resolved Hide resolved
r#type: "JsonWebKey2020".to_string(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nitro-neal didn't we standardize this to just JsonWebKey?

controller: did.clone(),
public_key_jwk: public_jwk,
};

let document = Document {
id: did.clone(),
context: Some(vec!["https://www.w3.org/ns/did/v1".to_string()]),
verification_method: vec![verification_method],
..Default::default()
};

Ok(DidWeb {
did: Did::new(&did)?,
document,
})
}

pub async fn from_uri(uri: &str) -> Result<Self> {
let resolution_result = DidWeb::resolve(uri);
match resolution_result.document {
Expand Down
1 change: 1 addition & 0 deletions crates/web5_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ chrono = { workspace = true }
clap = { version = "4.5.7", features = ["derive"] }
serde_json = { workspace = true }
web5 = { path = "../web5" }
url = "2.5.2"
uuid = { workspace = true }
63 changes: 60 additions & 3 deletions crates/web5_cli/src/dids/create.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use clap::Subcommand;
use url::Url;
use std::sync::Arc;
use web5::{
crypto::dsa::ed25519::{Ed25519Generator, Ed25519Signer},
dids::{
methods::{did_dht::DidDht, did_jwk::DidJwk},
methods::{did_dht::DidDht, did_jwk::DidJwk, did_web::DidWeb},
portable_did::PortableDid,
},
};
Expand All @@ -18,6 +19,10 @@ pub enum Commands {
},
Web {
domain: String,
#[arg(long)]
no_indent: bool,
#[arg(long)]
json_escape: bool,
},
Dht {
#[arg(long)]
Expand Down Expand Up @@ -63,8 +68,60 @@ impl Commands {

print_portable_did(portable_did, no_indent, json_escape);
}
Commands::Web { domain: _ } => {
println!("🚧 not currently supported 🚧");
Commands::Web {
domain,
no_indent,
json_escape,
} => {
let private_jwk = Ed25519Generator::generate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that with did web especially, we should allow people to pass in the own private key / portable did. We can add this as a feature in the future though

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, on the private key input (portable DID slightly different use case I haven't thought through -- creating a new DID with an existing DID), but yeah not a priority at this moment

let mut public_jwk = private_jwk.clone();
public_jwk.d = None;

let valid_url = if domain.starts_with("http://") || domain.starts_with("https://") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is really amazing code @diehuxx 👏

so good, in fact, I'm going to move it inside the DidWeb::new() function, so that we enable this DX not just for the CLI experience but also for application-level use cases. I feel confident exposing this code such that we can expose to developers, "send in any endpoint and we'll create a did:web instance out of it"

also I spy a slight bug in the case wherein both a .well-known and did.json are passed in, like in Example 4 here

let url = Url::parse(domain).expect("Invalid URL");

// Ensure "http://" is only allowed for localhost or 127.0.0.1
if url.scheme() == "http" && !(url.host_str() == Some("localhost") || url.host_str() == Some("127.0.0.1")) {
panic!("Only https is allowed except for localhost or 127.0.0.1 with http");
}

// Get the trimmed URL string without the scheme
let trimmed_url = url[url::Position::BeforeHost..].to_string();

// Remove the scheme
let normalized = if trimmed_url.starts_with("//") {
&trimmed_url[2..]
} else {
&trimmed_url
};

normalized.to_string()
} else {
Url::parse(&format!("https://{}", domain)).expect("Invalid URL");
domain.clone()
};

let normalized = if valid_url.ends_with("/did.json") {
valid_url.trim_end_matches("/did.json").to_string()
} else if valid_url.ends_with("/.well-known") {
valid_url.trim_end_matches("/.well-known").to_string()
} else if valid_url.ends_with("/") {
valid_url.trim_end_matches("/").to_string()
} else {
valid_url.clone()
};

let encoded_domain = normalized.replace(":", "%3A");
let encoded_domain = encoded_domain.replace("/", ":");

let did_web = DidWeb::new(&encoded_domain, public_jwk).unwrap();
let portable_did = PortableDid {
did_uri: did_web.did.uri,
document: did_web.document,
private_jwks: vec![private_jwk],
};

print_portable_did(portable_did, no_indent, json_escape)
}
Commands::Dht {
no_publish,
Expand Down
7 changes: 6 additions & 1 deletion docs/API_DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**Last Updated** May 30, 2024

**Version** 2.0.0
**Version** 2.1.0

**[Custom DSL](./CUSTOM_DSL.md) Version**: 0.1.0

Expand Down Expand Up @@ -582,8 +582,13 @@ resolution_result = DidJwk.resolve(uri)

### `DidWeb`

> [!NOTE]
>
> The `CONSTRUCTOR(domain: string, public_jwk: Jwk)` does not publish the DID Document to a host, but merely creates the instance of the `did:web` in the local scope.

```pseudocode!
CLASS DidWeb
CONSTRUCTOR(domain: string, public_jwk: Jwk)
CONSTRUCTOR(uri: string)
STATIC METHOD resolve(uri: string): ResolutionResult
```
Expand Down
Loading