identity_kit/methods/mod.rs
1//! DID methods: `did:key` ([`key`]), `did:jwk` ([`jwk`]) and `did:web` ([`web`]).
2//!
3//! Each method is a type implementing [`DidResolver`] and [`DidProducer`], registered with a
4//! [`Resolver`] through [`ResolverBuilder::with`]; [`default_methods`] registers all three at
5//! once. The methods are behind the `did-key`, `did-jwk` and `did-web` Cargo features, all on
6//! by default; `did:web` needs an [`HttpClient`](web::HttpClient), and the `reqwest` feature
7//! provides [`ReqwestClient`](web::ReqwestClient), a hardened default.
8//!
9//! # Examples
10//!
11//! ```
12//! # #[cfg(all(feature = "did-key", feature = "did-jwk", feature = "reqwest"))]
13//! # tokio::runtime::Builder::new_current_thread().enable_all().build()?.block_on(async {
14//! use identity_kit::did::DidUrl;
15//! use identity_kit::methods::web::ReqwestClient;
16//!
17//! let resolver = identity_kit::methods::default_methods(ReqwestClient::new()?).build();
18//! assert_eq!(resolver.methods(), ["jwk", "key", "web"]);
19//!
20//! // Embedded methods need no network: the document is derived from the DID.
21//! let key = DidUrl::parse(
22//! "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
23//! )?;
24//! let jwk = resolver.resolve_public_key(&key, None).await?;
25//! assert_eq!(jwk.crv(), Some("Ed25519"));
26//! # Ok::<(), Box<dyn std::error::Error>>(())
27//! # })?;
28//! # Ok::<(), Box<dyn std::error::Error>>(())
29//! ```
30
31#[cfg(feature = "did-jwk")]
32#[cfg_attr(docsrs, doc(cfg(feature = "did-jwk")))]
33pub mod jwk;
34#[cfg(feature = "did-key")]
35#[cfg_attr(docsrs, doc(cfg(feature = "did-key")))]
36pub mod key;
37#[cfg(feature = "did-web")]
38#[cfg_attr(docsrs, doc(cfg(feature = "did-web")))]
39pub mod web;
40
41#[cfg(feature = "did-jwk")]
42pub use jwk::DidJwk;
43#[cfg(feature = "did-key")]
44pub use key::DidKey;
45#[cfg(feature = "did-web")]
46pub use web::DidWeb;
47
48use crate::did::{
49 APPLICATION_DID_JSON, APPLICATION_DID_LD_JSON, ResolutionError, ResolutionOptions,
50};
51#[cfg(any(feature = "did-key", feature = "did-jwk"))]
52use crate::jose::Jwk;
53#[cfg(any(feature = "did-key", feature = "did-jwk"))]
54use crate::resolver::ProduceError;
55#[allow(unused_imports)]
56use crate::resolver::{DidProducer, DidResolver, Resolver, ResolverBuilder};
57
58/// A [`ResolverBuilder`] with every method this crate ships registered, with their defaults:
59/// `did:key` ([`DidKey`]), `did:jwk` ([`DidJwk`]) and `did:web` over `http`.
60///
61/// Register a configured method afterwards to replace a default, and add a cache with
62/// [`ResolverBuilder::cache`] before [`build`](ResolverBuilder::build).
63#[cfg(feature = "did-web")]
64#[cfg_attr(docsrs, doc(cfg(feature = "did-web")))]
65pub fn default_methods(http: impl web::HttpClient + 'static) -> ResolverBuilder {
66 let builder = Resolver::builder();
67 #[cfg(feature = "did-key")]
68 let builder = builder.with(DidKey::default());
69 #[cfg(feature = "did-jwk")]
70 let builder = builder.with(DidJwk);
71 builder.with(DidWeb::new(http))
72}
73
74/// The one key an embedded method takes.
75#[cfg(any(feature = "did-key", feature = "did-jwk"))]
76fn single_key(keys: &[Jwk]) -> Result<&Jwk, ProduceError> {
77 match keys {
78 [] => Err(ProduceError::NoKeys),
79 [key] => Ok(key),
80 _ => Err(ProduceError::TooManyKeys {
81 max: 1,
82 actual: keys.len(),
83 }),
84 }
85}
86
87/// The media types a method of this crate can serve, which are all the JSON ones.
88const JSON_MEDIA_TYPES: [&str; 3] = [
89 APPLICATION_DID_JSON,
90 APPLICATION_DID_LD_JSON,
91 "application/json",
92];
93
94/// Refuses an `accept` option asking for anything but a JSON representation.
95fn check_accept(options: &ResolutionOptions) -> Result<(), ResolutionError> {
96 match options.accept.as_deref() {
97 None => Ok(()),
98 Some(accept) if JSON_MEDIA_TYPES.contains(&accept) => Ok(()),
99 Some(_) => Err(ResolutionError::RepresentationNotSupported),
100 }
101}