lit_node_core/models/
auth_sig.rs

1use crate::AuthMaterialType;
2use crate::constants::{
3    AUTH_SIG_BLS_NETWORK_SIG_ALGO, AUTH_SIG_DERIVED_VIA_BLS_NETWORK_SIG,
4    AUTH_SIG_DERIVED_VIA_CONTRACT_SIG, AUTH_SIG_DERIVED_VIA_CONTRACT_SIG_SHA256,
5    AUTH_SIG_DERIVED_VIA_SESSION_SIG, AUTH_SIG_SESSION_SIG_ALGO, Chain,
6};
7use serde::de::{MapAccess, Visitor};
8use serde::{Deserialize, Deserializer, Serialize};
9use std::fmt;
10
11/// This struct is used both to represent various authentication material,
12/// e.g. wallet sigs, session sigs or cosmos auth sigs etc.
13#[derive(Serialize, Clone, Default, PartialEq, Eq)]
14#[cfg_attr(test, derive(Debug))]
15#[serde(rename_all = "camelCase")]
16pub struct JsonAuthSig {
17    pub sig: String,
18    pub derived_via: String,
19    pub signed_message: String,
20
21    // TODO: Make this private once extract_user_address has stabilized
22    pub address: String,
23    pub algo: Option<String>,
24
25    #[serde(skip)]
26    pub auth_material_type: AuthMaterialType,
27
28    /// The chain that the auth sig has been validated against.
29    ///
30    /// This is None if the auth sig has not been validated yet.
31    #[serde(skip)]
32    pub chain: Option<Chain>,
33}
34
35#[cfg(not(test))]
36impl fmt::Debug for JsonAuthSig {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        f.debug_struct("JsonAuthSig")
39            .field("sig", &"****filtered****")
40            .field("derived_via", &self.derived_via)
41            .field("signed_message", &self.signed_message)
42            .field("address", &self.address)
43            .field("algo", &self.algo)
44            .field("auth_material_type", &self.auth_material_type)
45            .field("chain", &self.chain)
46            .finish()
47    }
48}
49
50impl fmt::Display for JsonAuthSig {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        write!(
53            f,
54            "JsonAuthSig {{ sig: ****filtered****, derived_via: {}, signed_message: {}, address: {}, algo: {:?}, auth_material_type: {:?}, chain: {:?} }}",
55            self.derived_via,
56            self.signed_message,
57            self.address,
58            self.algo,
59            self.auth_material_type,
60            self.chain
61        )
62    }
63}
64
65impl JsonAuthSig {
66    pub fn new(
67        sig: String,
68        derived_via: String,
69        signed_message: String,
70        address: String,
71        algo: Option<String>,
72    ) -> Self {
73        JsonAuthSig {
74            sig,
75            derived_via,
76            signed_message,
77            address,
78            algo,
79            auth_material_type: AuthMaterialType::default(),
80            chain: None,
81        }
82    }
83
84    pub fn new_with_type(
85        sig: String,
86        derived_via: String,
87        signed_message: String,
88        address: String,
89        algo: Option<String>,
90        auth_material_type: AuthMaterialType,
91        chain: Option<Chain>,
92    ) -> Self {
93        JsonAuthSig {
94            sig,
95            derived_via,
96            signed_message,
97            address,
98            algo,
99            auth_material_type,
100            chain,
101        }
102    }
103
104    /// Always defaults to interpreting as a wallet sig. This is only because
105    /// we don't want to break clients too much.
106    ///
107    /// TODO: After a stabilization period, we should make our pattern matching
108    /// stricter and perhaps turn this function to returning a core::Result.
109    #[allow(clippy::collapsible_if)]
110    pub fn determine_auth_material_type(
111        derived_via: &str,
112        algo: &Option<String>,
113    ) -> AuthMaterialType {
114        if let Some(algo) = algo {
115            if derived_via == AUTH_SIG_DERIVED_VIA_SESSION_SIG && algo == AUTH_SIG_SESSION_SIG_ALGO
116            {
117                return AuthMaterialType::SessionSig;
118            }
119        }
120
121        if derived_via == AUTH_SIG_DERIVED_VIA_CONTRACT_SIG
122            || derived_via == AUTH_SIG_DERIVED_VIA_CONTRACT_SIG_SHA256
123        {
124            return AuthMaterialType::ContractSig;
125        }
126
127        if let Some(algo) = algo {
128            if derived_via == AUTH_SIG_DERIVED_VIA_BLS_NETWORK_SIG
129                && algo == AUTH_SIG_BLS_NETWORK_SIG_ALGO
130            {
131                return AuthMaterialType::BLSNetworkSig;
132            }
133        }
134
135        AuthMaterialType::WalletSig
136    }
137}
138
139// Custom deserialization logic for JsonAuthSig
140
141#[derive(Deserialize)]
142#[serde(field_identifier, rename_all = "camelCase")]
143enum JsonAuthSigField {
144    Sig,
145    DerivedVia,
146    SignedMessage,
147    Address,
148    Algo,
149}
150
151impl<'de> Deserialize<'de> for JsonAuthSig {
152    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
153    where
154        D: Deserializer<'de>,
155    {
156        deserializer.deserialize_map(JsonAuthSigVisitor)
157    }
158}
159
160struct JsonAuthSigVisitor;
161
162impl<'de> Visitor<'de> for JsonAuthSigVisitor {
163    type Value = JsonAuthSig;
164
165    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
166        write!(
167            formatter,
168            "a map with keys sig, derivedVia, signedMessage, address, and optionally algo"
169        )
170    }
171
172    fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
173    where
174        M: MapAccess<'de>,
175    {
176        let mut sig = None;
177        let mut derived_via = None;
178        let mut signed_message = None;
179        let mut address = None;
180        let mut algo = None;
181
182        while let Some(key) = map.next_key()? {
183            match key {
184                JsonAuthSigField::Sig => {
185                    if sig.is_some() {
186                        return Err(serde::de::Error::duplicate_field("sig"));
187                    }
188                    sig = Some(map.next_value()?);
189                }
190                JsonAuthSigField::DerivedVia => {
191                    if derived_via.is_some() {
192                        return Err(serde::de::Error::duplicate_field("derived_via"));
193                    }
194                    derived_via = Some(map.next_value()?);
195                }
196                JsonAuthSigField::SignedMessage => {
197                    if signed_message.is_some() {
198                        return Err(serde::de::Error::duplicate_field("signed_message"));
199                    }
200                    signed_message = Some(map.next_value()?);
201                }
202                JsonAuthSigField::Address => {
203                    if address.is_some() {
204                        return Err(serde::de::Error::duplicate_field("address"));
205                    }
206                    address = Some(map.next_value()?);
207                }
208                JsonAuthSigField::Algo => {
209                    if algo.is_some() {
210                        return Err(serde::de::Error::duplicate_field("algo"));
211                    }
212                    algo = map.next_value()?;
213                }
214            }
215        }
216
217        let sig: String = sig.ok_or_else(|| serde::de::Error::missing_field("sig"))?;
218        let derived_via: String =
219            derived_via.ok_or_else(|| serde::de::Error::missing_field("derived_via"))?;
220        let signed_message: String =
221            signed_message.ok_or_else(|| serde::de::Error::missing_field("signed_message"))?;
222        let address: String = address.ok_or_else(|| serde::de::Error::missing_field("address"))?;
223
224        // Determine the auth material type
225        let auth_material_type = JsonAuthSig::determine_auth_material_type(&derived_via, &algo);
226
227        Ok(JsonAuthSig::new_with_type(
228            sig,
229            derived_via,
230            signed_message,
231            address,
232            algo,
233            auth_material_type,
234            None,
235        ))
236    }
237}
238
239/// The auth sig used when calling admin endpoints
240#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
241#[serde(rename_all = "camelCase")]
242pub struct AdminAuthSig {
243    /// The inner auth sig
244    pub auth_sig: JsonAuthSig,
245}