Skip to main content

solid_pod_rs/
lib.rs

1//! Framework-agnostic Rust library for serving Solid Protocol 0.11 pods.
2//!
3//! `solid-pod-rs` provides LDP resource and container semantics, Web Access
4//! Control (WAC 1.x + 2.0), WebID profile documents, Solid-OIDC 0.1,
5//! NIP-98 HTTP auth, and Solid Notifications 0.2 -- all without coupling
6//! to a specific HTTP framework. Wire it into actix-web, axum, hyper, or
7//! anything else; the crate never mounts routes itself. On top of the Solid
8//! core it adds two composable **provenance primitives** ([`provenance`]):
9//! cheap, always-on **git-marks** (every pod write captured as a git commit
10//! + a PROV-O sidecar) and expensive, opt-in **block-trails** (a
11//! Bitcoin-taproot-anchored, hash-chained state trail — [`mrc20`] /
12//! [`bitcoin_tx`]) — and a routed, sovereign HTTP-402 economy: a `did:nostr`-keyed
13//! [Web Ledger](payments), `acl:PaymentCondition` ([`wac`]) access gating, an MRC20
14//! deposit path, and a peer order book + constant-product AMM ([`trading`]).
15//! The HTTP routing for the 402 economy and the `_prov` provenance API lives in
16//! the sibling [`solid-pod-rs-server`](https://docs.rs/solid-pod-rs-server). See
17//! [ADR-059](https://docs.rs/crate/solid-pod-rs/latest/source/docs/adr/ADR-059-provenance-primitives-block-trails-git-marks.md).
18//!
19//! For a turnkey binary, use the sibling crate
20//! [`solid-pod-rs-server`](https://docs.rs/solid-pod-rs-server).
21//!
22//! ## Feature flags
23//!
24//! | Flag | Default | Purpose |
25//! |-------------------------|:-------:|-----------------------------------------------|
26//! | `core` | off | Pure-logic surfaces only — wasm32 / CF Workers. |
27//! | `std` | on | std lib (always; reserved for future no_std).  |
28//! | `embedded-docs` | off | Embed the Diataxis `docs/` tree as `pub static DOCS_DIR` (re-exports `include_dir`); consumed by `solid-pod-rs-server`'s MCP docs tools + docs.rs. |
29//! | `tokio-runtime` | on | Tokio + tokio-tungstenite + futures-util.       |
30//! | `notifications` | on | WebSocketChannel2023 + WebhookChannel2023.      |
31//! | `fs-backend` | on | POSIX filesystem storage. |
32//! | `memory-backend` | on | In-process `HashMap` storage (tests/demos). |
33//! | `s3-backend` | off | AWS S3 / S3-compatible object stores. |
34//! | `oidc` | off | Solid-OIDC 0.1 + DPoP. |
35//! | `dpop-replay-cache` | off | DPoP `jti` replay cache (pulls `oidc`). |
36//! | `nip98-schnorr` | off | BIP-340 signature verification for NIP-98. Verification is **unconditional and fail-closed**: without this feature the verifier returns [`PodError::Unsupported`] rather than accepting a forged pubkey after structural checks alone. |
37//! | `nip98-replay` | off | NIP-98 single-use replay guard (`auth::replay::Nip98ReplayCache`) — bounded process-local LRU keyed on the canonical event id; closes the ±120s replay window the stateless verifier leaves open. |
38//! | `jss-v04` | off | JSS-parity umbrella (ADR-056); no-op alone — sub-features below switch one bounded context each on. |
39//! | `acl-origin` | off | WAC `acl:origin` enforcement (pulls `jss-v04`). Wired into the request path in `solid-pod-rs-server` (the request `Origin` is threaded into the evaluator `RequestContext`). Note: `acl:origin` is the only WAC 2.0 condition satisfiable end-to-end today — `client_id`/`issuer` conditions still evaluate deny (no authenticated OIDC client_id/issuer is surfaced into the context yet). |
40//! | `security-primitives` | off | SSRF guard + dotfile allowlist (pulls `jss-v04`). |
41//! | `legacy-notifications` | off | `solid-0.1` WebSocket adapter (SolidOS). |
42//! | `config-loader` | off | Layered config loader with `JSS_*` env vars + YAML/TOML. |
43//! | `webhook-signing` | off | RFC 9421 Ed25519 webhook signing. |
44//! | `rate-limit` | off | Sliding-window LRU rate limiter + CORS. |
45//! | `quota` | off | Per-pod `.quota.json` sidecar (atomic writes). |
46//! | `did-nostr-types` | off | Canonical did:nostr types (wasm32-safe). |
47//! | `did-nostr` | off | did:nostr DID-Doc ↔ WebID resolver in [`interop`]. |
48//! | `mrc20` | off | BIP-341 taproot key chaining + anchor verify/build for MRC20 / block-trails. |
49//! | `lws-cid` | off | LWS 1.0 CID self-signed JWT verifier (ES256K). |
50//! | `lws-cid-p256` | off | LWS-CID + ES256 (P-256) algorithm. |
51//! | `lws-cid-eddsa` | off | LWS-CID + EdDSA (Ed25519) algorithm. |
52//! | `lws-cid-full` | off | LWS-CID with all algorithms. |
53//! | `provision-keys` | off | Gates the optional `ProvisionPlan::provision_keys` field (IdP key provisioner extension point). |
54//! | `nip05-endpoint` | off | Pod-resident `GET /.well-known/nostr.json` (pulls `did-nostr`). |
55//! | `export-jsonld` | off | JSON-LD time-chain pod export (`GET /api/exports/all`). |
56//! | `git-auto-init` | off | `GitInitHook` trait + `provision_pod_ext` (on-demand `git init` on first push; impl in `solid-pod-rs-git`). |
57//!
58//! On `wasm32-unknown-unknown` targets the `getrandom` crate is pulled in with
59//! its `js` feature (transitively required by `uuid`/`rand`) so randomness
60//! resolves via `crypto.getRandomValues()`; this is a target-gated dependency,
61//! not a cargo feature you enable.
62//!
63//! `core` consumers wire the crate via `default-features = false,
64//! features = ["core"]` and get only the pure-logic surfaces (no
65//! tokio, no reqwest, no DNS resolver, no filesystem). See
66//! `RELEASE_NOTES.md` v0.4.0-alpha.3 for the absorbed surfaces map.
67//!
68//! ## Module overview
69//!
70//! | Module | Responsibility |
71//! |-----------------|--------------------------------------------------------------|
72//! | [`storage`] | `Storage` trait + FS / Memory / S3 backends. |
73//! | [`ldp`] | Resources, containers, content negotiation, PATCH, `Prefer`. |
74//! | [`wac`] | Access control evaluator + WAC 2.0 conditions framework. |
75//! | [`webid`] | WebID profile documents (emits `solid:oidcIssuer` + CID). |
76//! | [`mashlib`] | SolidOS data-browser HTML wrapper + data-island embed.    |
77//! | [`auth`] | NIP-98 HTTP auth (unconditional fail-closed Schnorr verify + single-use replay guard) + LWS-CID self-signed JWT verifier. |
78//! | [`payments`] | HTTP 402, Web Ledgers, multi-chain TXO, payment store. |
79//! | [`mrc20`] | MRC20 state chains, JCS, BIP-341 key chaining.       |
80//! | [`provenance`] | git-mark / block-trail provenance primitives + PROV-O.  |
81//! | [`trading`] | Peer-to-peer order book + AMM constant-product pool.  |
82//! | [`notifications`] | WebSocket, Webhook (RFC 9421 signed), legacy adapter. |
83//! | [`error`] | Crate-wide [`PodError`] error type. |
84//! | [`config`] | Layered configuration schema. |
85//! | [`security`] | SSRF guard, dotfile allowlist, CORS, rate limiter. |
86//! | [`quota`] | Per-pod byte-quota enforcement. |
87//! | [`multitenant`] | `PodResolver` trait; path + subdomain modes. |
88//! | [`interop`] | `.well-known/solid`, WebFinger, NodeInfo, did:nostr. |
89//! | [`did_nostr_types`] | Canonical `did:nostr` types (wasm32-safe, `core`). |
90//! | [`provision`] | Pod bootstrap (WebID + containers + type indexes + ACL). |
91//!
92//! ## Quick start
93//!
94//! ```rust,no_run
95//! use solid_pod_rs::storage::memory::MemoryBackend;
96//! use solid_pod_rs::{Storage, evaluate_access, AccessMode};
97//! use bytes::Bytes;
98//! use std::sync::Arc;
99//!
100//! # tokio::runtime::Runtime::new().unwrap().block_on(async {
101//! // 1. Create a storage backend.
102//! let store = Arc::new(MemoryBackend::new());
103//!
104//! // 2. PUT a resource.
105//! store.put("/hello.txt", Bytes::from("world"), "text/plain").await.unwrap();
106//!
107//! // 3. GET it back.
108//! let (body, meta) = store.get("/hello.txt").await.unwrap();
109//! assert_eq!(&body[..], b"world");
110//! assert_eq!(meta.content_type, "text/plain");
111//!
112//! // 4. WAC evaluation (no ACL document = deny by default).
113//! let allowed = evaluate_access(None, Some("https://alice.example/profile/card#me"),
114//!     "/hello.txt", AccessMode::Read, None);
115//! assert!(!allowed);
116//! # });
117//! ```
118//!
119//! ## Attribution
120//!
121//! Rust port of JavaScriptSolidServer. See NOTICE for provenance.
122
123#![doc = include_str!("../README.md")]
124#![deny(unsafe_code)]
125#![warn(rust_2018_idioms)]
126
127// ---------------------------------------------------------------------------
128// Always-compiled (`core`) modules.
129//
130// Pure-logic surfaces: parsers, validators, type definitions. None of
131// these reach for tokio, reqwest, or notify directly. Wasm32 / CF
132// Workers consumers wire these via
133// `default-features = false, features = ["core"]`.
134// ---------------------------------------------------------------------------
135pub mod auth;
136/// Bitcoin taproot transaction building (block-trail write-side). The module's
137/// own inner `#![cfg(all(feature = "mrc20", not(target_arch = "wasm32")))]`
138/// gates it: it compiles to nothing on wasm or without the `mrc20` feature, so
139/// the tx-building never leaks into the wasm `core` surface (ADR-059 D4).
140pub mod bitcoin_tx;
141pub mod config;
142pub mod error;
143pub mod interop;
144pub mod ldp;
145pub mod mashlib;
146pub mod metrics;
147pub mod mrc20;
148pub mod multitenant;
149pub mod payments;
150pub mod provenance;
151pub mod security;
152pub mod trading;
153pub mod wac;
154pub mod webid;
155
156// ---------------------------------------------------------------------------
157// `did-nostr-types`-gated module.
158//
159// Canonical did:nostr types (NostrPubkey, DID-Doc renderers, ServiceEntry)
160// behind a lightweight feature flag. No runtime deps — wasm32 / CF Workers
161// consumers get these via `core`.
162// ---------------------------------------------------------------------------
163#[cfg(feature = "did-nostr-types")]
164pub mod did_nostr_types;
165
166// ---------------------------------------------------------------------------
167// `tokio-runtime`-gated modules.
168//
169// These pull tokio (mpsc, fs, broadcast) or reqwest (HTTP client) and
170// are unavailable to `core` consumers. They are wired in by the
171// `default` feature set so the existing surface from 0.4.0-alpha.2 is
172// preserved bit-for-bit on native builds.
173// ---------------------------------------------------------------------------
174#[cfg(feature = "notifications")]
175pub mod notifications;
176#[cfg(feature = "tokio-runtime")]
177pub mod provision;
178#[cfg(feature = "tokio-runtime")]
179pub mod quota;
180#[cfg(feature = "tokio-runtime")]
181pub mod storage;
182
183#[cfg(feature = "oidc")]
184pub mod oidc;
185
186// ---------------------------------------------------------------------------
187// JSS v0.0.190 Phase 1 port (issue #437) — pod data export.
188//
189// Parity row 198. Default-off (`export-jsonld`). `export_pod_jsonld` is
190// fully implemented and tested (bodies landed in 0.4.0-alpha.11; no
191// `todo!()`); the library surface is stable for downstream consumers
192// (NRF, dreamlab-ai-website). The `export-jsonld` feature pulls
193// `tokio-runtime` and is therefore native-only — the wasm32 CF-Workers
194// pod build cannot compile or serve it. Only native `solid-pod-rs-server`
195// deployments expose the export: it is served (owner-WAC-gated) at
196// `GET /api/exports/all`, registered under the `export-jsonld` feature.
197// Library consumers can also call `export_pod_jsonld` directly.
198// ---------------------------------------------------------------------------
199#[cfg(feature = "export-jsonld")]
200pub mod export;
201
202/// Transport-agnostic HTTP / WebSocket handler drivers. Consumers wire
203/// these into their HTTP framework of choice. Feature-gated; present
204/// only when at least one handler is enabled. Respects the F7
205/// library-server boundary — this crate never mounts routes itself.
206#[cfg(feature = "legacy-notifications")]
207pub mod handlers;
208
209// ---------------------------------------------------------------------------
210// `core` re-exports — always available.
211// ---------------------------------------------------------------------------
212pub use auth::nip98::Nip98Verifier;
213pub use auth::self_signed::{
214    CidVerifier, ProofEnvelope, SelfSignedError, SelfSignedVerifier, VerifiedSubject,
215};
216pub use error::PodError;
217pub use interop::{
218    dev_session, nip05_document, verify_nip05, webfinger_response, well_known_solid, DevSession,
219    Nip05Document, SolidWellKnown, WebFingerJrd, WebFingerLink,
220};
221pub use ldp::{
222    apply_json_patch, apply_n3_patch, apply_patch_to_absent, apply_sparql_patch, cache_control_for,
223    evaluate_preconditions, is_rdf_content_type, link_headers, negotiate_format, not_found_headers,
224    options_for, parse_range_header, parse_range_header_v2, patch_dialect_from_mime,
225    server_managed_triples, slice_range, vary_header, ByteRange, ConditionalOutcome,
226    ContainerRepresentation, Graph, OptionsResponse, PatchCreateOutcome, PatchDialect,
227    PatchOutcome, PreferHeader, RangeOutcome, RdfFormat, Term, Triple, ACCEPT_PATCH, ACCEPT_POST,
228    CACHE_CONTROL_RDF, SPARQL_UPDATE_MAX_BYTES,
229};
230pub use mashlib::{MashlibConfig, MashlibMode, DATA_ISLAND_MAX_BYTES};
231pub use metrics::SecurityMetrics;
232pub use multitenant::{PathResolver, PodResolver, ResolvedPath, SubdomainResolver};
233pub use provenance::{
234    prov_ttl, AnchorPolicy, BlockAnchorer, BlockTrailAnchor, BlocktrailEnvelope, BlocktrailTxo,
235    ClosedEpoch, EpochAccumulator, GitMark, GitMarkEnvelope, GitMarker, MerkleProof,
236    ProvenanceError, ProvenanceLog, ProvenanceMark, WriteRecord,
237};
238pub use security::{is_path_allowed, DotfileAllowlist, DotfileError, DotfilePathError};
239pub use wac::{
240    check_origin, effective_acl_target, evaluate_access, evaluate_access_with_groups,
241    extract_origin_patterns, method_to_mode, mode_name, parse_turtle_acl,
242    protected_resource_for_acl, serialize_turtle_acl, wac_allow_header, AccessMode, AclDocument,
243    GroupMembership, Origin, OriginDecision, OriginPattern, StaticGroupMembership,
244};
245pub use webid::{
246    extract_nostr_pubkey, extract_oidc_issuer, generate_webid_html,
247    generate_webid_html_with_issuer, validate_webid_html,
248};
249
250// ---------------------------------------------------------------------------
251// `tokio-runtime`-gated re-exports.
252// ---------------------------------------------------------------------------
253#[cfg(feature = "tokio-runtime")]
254pub use provision::{
255    check_admin_override, provision_pod, AdminOverride, ProvisionOutcome, ProvisionPlan,
256    QuotaTracker,
257};
258#[cfg(feature = "quota")]
259pub use quota::FsQuotaStore;
260#[cfg(feature = "tokio-runtime")]
261pub use quota::{QuotaExceeded, QuotaPolicy, QuotaUsage};
262#[cfg(feature = "tokio-runtime")]
263pub use security::{is_safe_url, resolve_and_check, IpClass, SsrfError, SsrfPolicy};
264#[cfg(feature = "tokio-runtime")]
265pub use storage::{ResourceMeta, Storage, StorageEvent};
266
267// ---------------------------------------------------------------------------
268// JSS v0.0.190 Phase 1 port — export re-exports (implemented, not stubs).
269// ---------------------------------------------------------------------------
270#[cfg(feature = "export-jsonld")]
271pub use export::{
272    export_pod_jsonld, ExportOptions, PodExportBundle, PodExportEntry, EXPORT_CONTENT_TYPE,
273    EXPORT_JSONLD_CONTEXT,
274};
275
276// ---------------------------------------------------------------------------
277// Embedded documentation tree (feature = "embedded-docs")
278// ---------------------------------------------------------------------------
279
280/// Re-exported so dependants can name `include_dir::Dir` without taking a
281/// direct dependency on the embedding crate.
282#[cfg(feature = "embedded-docs")]
283pub use include_dir;
284
285/// The crate's Diataxis `docs/` tree, embedded at compile time. It lives
286/// here — inside the owning crate's package root — so dependants (notably
287/// `solid-pod-rs-server`'s MCP `list_docs`/`read_docs` tools) serve it from
288/// a self-contained binary even when built from the registry tarball, where
289/// a `../solid-pod-rs/docs` path escape does not exist.
290#[cfg(feature = "embedded-docs")]
291pub static DOCS_DIR: include_dir::Dir<'static> =
292    include_dir::include_dir!("$CARGO_MANIFEST_DIR/docs");