didbot_identity/lib.rs
1//! `did:web` minting, DID documents, and a resolver that works locally.
2//!
3//! Every agent context gets its own account, and every account needs a DID.
4//! `did:plc` would mean a registration round trip to a third-party directory
5//! for each one, so these are `did:web`: the DID is a hostname the deployment
6//! already controls, and minting is a string operation plus a document served
7//! at `/.well-known/did.json`.
8//!
9//! That choice has a consequence worth stating plainly. `did:web` "is
10//! inherently tied to the domain name used, and does not provide a mechanism
11//! for migration or recovering from loss of control of the domain name". An
12//! agent identity lives and dies with the zone it was minted under.
13//!
14//! atproto constrains `did:web` more tightly than the W3C draft does, and the
15//! constraints are the interesting part of this crate:
16//!
17//! * "only hostname-level `did:web` DIDs are supported: path-based DIDs are
18//! not supported" — so each agent is a *subdomain*, not a path.
19//! * "The special `localhost` hostname is allowed, but only in testing and
20//! development environments."
21//! * "Port numbers (with separating colon hex-encoded) are only allowed for
22//! `localhost`, and only in testing and development."
23//! * A hard limit of 2048 characters on the identifier.
24//!
25//! The percent-encoded port is where existing tooling falls down, which is
26//! why [`mod@resolve`] exists here rather than being borrowed; see that module.
27//!
28//! Handles get the same treatment from the other end. atproto's rule is that
29//! resolution is **bidirectional**: a handle resolves to a DID, and that DID's
30//! document must claim the handle back in `alsoKnownAs`. Half of it is not a
31//! weaker version of the whole — a handle that resolves to a DID which does
32//! not claim it is somebody asserting a name they do not hold.
33//! [`handle::atproto_did_url`] and [`handle::parse_atproto_did`] are the
34//! forward half and [`DidDocument::claims_handle`] is the reverse, and no
35//! transport for either lives here: this crate owns the rules, and the
36//! services that already hold an HTTP client own the fetch. The DNS TXT
37//! resolution method is not implemented.
38
39#![forbid(unsafe_code)]
40
41pub mod did;
42pub mod document;
43pub mod handle;
44pub mod resolve;
45
46pub use did::{
47 canonical_did, hostname_is_at_or_below, validate_did, validate_label, AccountDid, DidError,
48 SyntaxError, Zone, ZoneRegistry, ZoneRegistryError, MAX_DID_LENGTH,
49};
50pub use document::{Claim, DidDocument, OidcIdentity, Service, VerificationMethod};
51pub use handle::{
52 atproto_did_url, has_disallowed_tld, parse_atproto_did, validate as validate_handle,
53 HandleError, ATPROTO_DID_PATH,
54};
55pub use resolve::{
56 document_url, resolve, DidDocumentSource, InMemoryDocuments, ResolveError,
57 DEFAULT_PLC_DIRECTORY,
58};