smart_id_rust_client/models/
interaction.rs1use crate::error::Result;
2use crate::error::SmartIdClientError;
3use base64::Engine;
4use base64::engine::general_purpose::STANDARD;
5use serde::{Deserialize, Serialize};
6use serde_with::skip_serializing_none;
7use sha2::{Digest, Sha256, Sha384, Sha512};
8use strum_macros::AsRefStr;
9use crate::models::signature::HashingAlgorithm;
10
11#[skip_serializing_none]
23#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Default, AsRefStr)]
24#[serde(rename_all = "camelCase")]
25#[strum(serialize_all = "camelCase")]
26#[non_exhaustive]
27pub enum InteractionFlow {
28 #[default]
29 DisplayTextAndPIN,
30 ConfirmationMessage,
31 VerificationCodeChoice,
32 ConfirmationMessageAndVerificationCodeChoice,
33}
34
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
46#[serde(tag = "type", rename_all = "camelCase")]
47#[non_exhaustive]
48pub enum Interaction {
49 #[serde(rename_all = "camelCase")]
50 DisplayTextAndPIN { display_text_60: String },
51 #[serde(rename_all = "camelCase")]
52 ConfirmationMessage { display_text_200: String },
53 #[serde(rename_all = "camelCase")]
54 VerificationCodeChoice { display_text_60: String },
55 #[serde(rename_all = "camelCase")]
56 ConfirmationMessageAndVerificationCodeChoice { display_text_200: String },
57}
58
59impl Interaction {
60 pub fn validate_text_length(&self) -> Result<()> {
61 match self {
62 Interaction::DisplayTextAndPIN { display_text_60 } => {
63 if display_text_60.len() > 60 {
64 return Err(SmartIdClientError::InvalidInteractionParametersException(
65 "Display text must be 60 characters or less",
66 ));
67 }
68 }
69 Interaction::ConfirmationMessage { display_text_200 } => {
70 if display_text_200.len() > 200 {
71 return Err(SmartIdClientError::InvalidInteractionParametersException(
72 "Display text must be 200 characters or less",
73 ));
74 }
75 }
76 Interaction::VerificationCodeChoice { display_text_60 } => {
77 if display_text_60.len() > 60 {
78 return Err(SmartIdClientError::InvalidInteractionParametersException(
79 "Display text must be 60 characters or less",
80 ));
81 }
82 }
83 Interaction::ConfirmationMessageAndVerificationCodeChoice { display_text_200 } => {
84 if display_text_200.len() > 200 {
85 return Err(SmartIdClientError::InvalidInteractionParametersException(
86 "Display text must be 200 characters or less",
87 ));
88 }
89 }
90 }
91 Ok(())
92 }
93}
94
95pub fn encode_interactions_base_64(interactions: &Vec<Interaction>) -> Result<String> {
97 let interactions_json = serde_json::to_string(&interactions)
98 .map_err(|e| SmartIdClientError::SerializationError(e.to_string()))?;
99 let base64_encoded =
100 base64::engine::general_purpose::STANDARD.encode(interactions_json.as_bytes());
101 Ok(base64_encoded)
102}
103
104pub fn hash_encode_digest(digest: &str, hashing_algorithm: &HashingAlgorithm) -> Result<String> {
105 let digest_bytes = STANDARD.decode(digest).map_err(
106 |e| SmartIdClientError::InvalidDigestException(format!("Failed to decode digest from base 64: {}", e)),
107 )?;
108
109 let hash_bytes = match hashing_algorithm {
110 HashingAlgorithm::sha_256 => {
111 let mut hasher = Sha256::new();
112 hasher.update(digest_bytes);
113 hasher.finalize().to_vec()
114 },
115 HashingAlgorithm::sha_384 => {
116 let mut hasher = Sha384::new();
117 hasher.update(digest_bytes);
118 hasher.finalize().to_vec()
119 },
120 HashingAlgorithm::sha_512 => {
121 let mut hasher = Sha512::new();
122 hasher.update(digest_bytes);
123 hasher.finalize().to_vec()
124 },
125 HashingAlgorithm::sha3_256 => {
126 let mut hasher = sha3::Sha3_256::new();
127 hasher.update(digest_bytes);
128 hasher.finalize().to_vec()
129 },
130 HashingAlgorithm::sha3_384 => {
131 let mut hasher = sha3::Sha3_384::new();
132 hasher.update(digest_bytes);
133 hasher.finalize().to_vec()
134 },
135 HashingAlgorithm::sha3_512 => {
136 let mut hasher = sha3::Sha3_512::new();
137 hasher.update(digest_bytes);
138 hasher.finalize().to_vec()
139 },
140 };
141
142 Ok(STANDARD.encode(hash_bytes))
143}
144
145#[cfg(test)]
147mod interaction_tests {
148 use super::*;
149 use serde_json;
150 use tracing_test::traced_test;
151
152 #[traced_test]
153 #[tokio::test]
154 async fn test_serializing_display_text_and_pin() {
155 let interaction = Interaction::DisplayTextAndPIN {
156 display_text_60: "Hello, World!".to_string(),
157 };
158 let serialized = serde_json::to_string(&interaction).unwrap();
159 assert_eq!(
160 serialized,
161 "{\"type\":\"displayTextAndPIN\",\"displayText60\":\"Hello, World!\"}"
162 );
163 }
164
165 #[traced_test]
166 #[tokio::test]
167 async fn test_validate_text_length_display_text_and_pin() {
168 let valid_interaction = Interaction::DisplayTextAndPIN {
169 display_text_60: "Valid text".to_string(),
170 };
171 assert!(valid_interaction.validate_text_length().is_ok());
172
173 let invalid_interaction = Interaction::DisplayTextAndPIN {
174 display_text_60: "This text is way too long and should cause an error because it exceeds the 60 character limit.".to_string(),
175 };
176 assert!(invalid_interaction.validate_text_length().is_err());
177 }
178
179 #[traced_test]
180 #[tokio::test]
181 async fn test_validate_text_length_confirmation_message() {
182 let valid_interaction = Interaction::ConfirmationMessage {
183 display_text_200: "Valid text".to_string(),
184 };
185 assert!(valid_interaction.validate_text_length().is_ok());
186
187 let invalid_interaction = Interaction::ConfirmationMessage {
188 display_text_200: "This text is way too long and should cause an error because it exceeds the 200 character limit. This text is way too long and should cause an error because it exceeds the 200 character limit. This text is way too long and should cause an error because it exceeds the 200 character limit.".to_string(),
189 };
190 assert!(invalid_interaction.validate_text_length().is_err());
191 }
192
193 #[traced_test]
194 #[tokio::test]
195 async fn test_validate_text_length_verification_code_choice() {
196 let valid_interaction = Interaction::VerificationCodeChoice {
197 display_text_60: "Valid text".to_string(),
198 };
199 assert!(valid_interaction.validate_text_length().is_ok());
200
201 let invalid_interaction = Interaction::VerificationCodeChoice {
202 display_text_60: "This text is way too long and should cause an error because it exceeds the 60 character limit.".to_string(),
203 };
204 assert!(invalid_interaction.validate_text_length().is_err());
205 }
206
207 #[traced_test]
208 #[tokio::test]
209 async fn test_validate_text_length_confirmation_message_and_verification_code_choice() {
210 let valid_interaction = Interaction::ConfirmationMessageAndVerificationCodeChoice {
211 display_text_200: "Valid text".to_string(),
212 };
213 assert!(valid_interaction.validate_text_length().is_ok());
214
215 let invalid_interaction = Interaction::ConfirmationMessageAndVerificationCodeChoice {
216 display_text_200: "This text is way too long and should cause an error because it exceeds the 200 character limit. This text is way too long and should cause an error because it exceeds the 200 character limit. This text is way too long and should cause an error because it exceeds the 200 character limit.".to_string(),
217 };
218 assert!(invalid_interaction.validate_text_length().is_err());
219 }
220
221 #[traced_test]
223 #[tokio::test]
224 async fn test_confirmation_interaction_base64_encoding() {
225 let interaction = Interaction::ConfirmationMessage {
226 display_text_200: "Longer description of the transaction context".to_string(),
227 };
228 let encoded = encode_interactions_base_64(&vec![interaction]).unwrap();
229 assert_eq!(encoded, "W3sidHlwZSI6ImNvbmZpcm1hdGlvbk1lc3NhZ2UiLCJkaXNwbGF5VGV4dDIwMCI6IkxvbmdlciBkZXNjcmlwdGlvbiBvZiB0aGUgdHJhbnNhY3Rpb24gY29udGV4dCJ9XQ==");
230 }
231
232 #[traced_test]
234 #[tokio::test]
235 async fn test_display_text_and_pin_base64_encoding() {
236 let interaction = Interaction::DisplayTextAndPIN {
237 display_text_60: "Log in to mobile banking app".to_string(),
238 };
239 let encoded = encode_interactions_base_64(&vec![interaction]).unwrap();
240 assert_eq!(encoded, "W3sidHlwZSI6ImRpc3BsYXlUZXh0QW5kUElOIiwiZGlzcGxheVRleHQ2MCI6IkxvZyBpbiB0byBtb2JpbGUgYmFua2luZyBhcHAifV0=");
241 }
242
243 #[traced_test]
245 #[tokio::test]
246 async fn test_multi_interaction_base64_encoding() {
247 let interactions = vec![
248 Interaction::ConfirmationMessage {
249 display_text_200: "Longer description of the transaction context".to_string(),
250 },
251 Interaction::DisplayTextAndPIN {
252 display_text_60: "Short description of the transaction context".to_string(),
253 },
254 ];
255 let encoded = encode_interactions_base_64(&interactions).unwrap();
256 assert_eq!(encoded, "W3sidHlwZSI6ImNvbmZpcm1hdGlvbk1lc3NhZ2UiLCJkaXNwbGF5VGV4dDIwMCI6IkxvbmdlciBkZXNjcmlwdGlvbiBvZiB0aGUgdHJhbnNhY3Rpb24gY29udGV4dCJ9LHsidHlwZSI6ImRpc3BsYXlUZXh0QW5kUElOIiwiZGlzcGxheVRleHQ2MCI6IlNob3J0IGRlc2NyaXB0aW9uIG9mIHRoZSB0cmFuc2FjdGlvbiBjb250ZXh0In1d");
257 }
258}
259
260