trustchain_http/
middleware.rs

1//! Middleware for Trustchain HTTP.
2use axum::{
3    body::Body,
4    extract::Path,
5    http::{Request, StatusCode},
6    middleware::Next,
7    response::IntoResponse,
8    Json,
9};
10use did_ion::{
11    sidetree::{DIDSuffix, Sidetree},
12    ION,
13};
14use lazy_static::lazy_static;
15use serde_json::json;
16use trustchain_ion::config::ion_config;
17use trustchain_ion::ion::IONTest;
18
19lazy_static! {
20    static ref ION_DID_PREFIX: String = format!("did:{}", ION::METHOD);
21    static ref ION_DID_TEST_PREFIX: String =
22        format!("{}:{}", &*ION_DID_PREFIX, IONTest::NETWORK.unwrap());
23}
24
25/// Generates an error message given DID and expected prefix.
26fn error_message(did: &str, expected_prefix: &str) -> serde_json::Value {
27    json!({
28        "error":
29            format!(
30                "DID: {} does not match expected prefix: {}",
31                did,
32                expected_prefix
33            )
34    })
35}
36
37fn validate_did_str(
38    did: &str,
39    mongo_database_ion_core: &str,
40) -> Result<(), (StatusCode, Json<serde_json::Value>)> {
41    let did_split = did.rsplit_once(':');
42    if did_split.is_none() {
43        return Err((
44            StatusCode::BAD_REQUEST,
45            Json(json!({"error": format!("InvalidDID: {}", did)})),
46        ));
47    }
48    let (did_prefix, ion_did_suffix) = did_split.unwrap();
49
50    // Only validate ION DIDs. Allow others to pass.
51    if did_prefix != *ION_DID_PREFIX && did_prefix != *ION_DID_TEST_PREFIX {
52        return Ok(());
53    }
54
55    // Validate the DID suffix given established DID method is ION.
56    let ion_did_suffix = DIDSuffix(ion_did_suffix.to_string());
57    if let Err(err) = ION::validate_did_suffix(&ion_did_suffix) {
58        return Err((
59            StatusCode::BAD_REQUEST,
60            Json(
61                json!({"error": format!("DID: {did} does not have a valid ION suffix with error: {err}")}),
62            ),
63        ));
64    };
65
66    // Validate the ION network prefix if testnet.
67    if mongo_database_ion_core.contains("testnet") && did_prefix != *ION_DID_TEST_PREFIX {
68        return Err((
69            StatusCode::BAD_REQUEST,
70            Json(error_message(did, &ION_DID_TEST_PREFIX)),
71        ));
72    }
73
74    // Validate the ION network prefix if mainnet.
75    if mongo_database_ion_core.contains("mainnet") && did_prefix != *ION_DID_PREFIX {
76        return Err((
77            StatusCode::BAD_REQUEST,
78            Json(error_message(did, &ION_DID_PREFIX)),
79        ));
80    }
81    Ok(())
82}
83// See [example](https://github.com/tokio-rs/axum/blob/v0.6.x/examples/consume-body-in-extractor-or-middleware/src/main.rs)
84// from axum with middleware that shows how to consume the request body upfront
85pub async fn validate_did(
86    Path(did): Path<String>,
87    request: Request<Body>,
88    next: Next<Body>,
89) -> impl IntoResponse {
90    tracing::info!(did);
91    match validate_did_str(&did, &ion_config().mongo_database_ion_core) {
92        Ok(_) => Ok(next.run(request).await),
93        Err(e) => Err(e),
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn test_strings() {
103        assert_eq!("did:ion", *ION_DID_PREFIX);
104        assert_eq!("did:ion:test", *ION_DID_TEST_PREFIX);
105    }
106
107    #[test]
108    fn test_valid_did() {
109        // Ok cases
110        for (did, network) in [
111            (
112                "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
113                "testnet",
114            ),
115            (
116                "did:ion:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
117                "mainnet",
118            ),
119            (
120                "did:key:z6MkhG98a8j2d3jqia13vrWqzHwHAgKTv9NjYEgdV3ndbEdD",
121                "testnet",
122            ),
123        ] {
124            assert!(validate_did_str(did, network).is_ok());
125        }
126        // Error cases
127        for (did, network) in [
128            // Invalid length
129            (
130                "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65",
131                "testnet",
132            ),
133            // Invalid suffix
134            (
135                "did:ion:test:1iAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
136                "testnet",
137            ),
138            // Invalid network
139            (
140                "did:ion:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
141                "testnet",
142            ),
143            // Invalid length
144            (
145                "did:ion:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65",
146                "mainnet",
147            ),
148            // Invalid suffix
149            (
150                "did:ion:1iAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
151                "mainnet",
152            ),
153            // Invalid network
154            (
155                "did:ion:test:EiAtHHKFJWAk5AsM3tgCut3OiBY4ekHTf66AAjoysXL65Q",
156                "mainnet",
157            ),
158        ] {
159            assert!(validate_did_str(did, network).is_err());
160        }
161    }
162}