1use super::{
4 encode_newline_data, get_or_error, parse_newline_data, ProtocolVersion, PROTOCOL_VERSIONS,
5};
6use crate::{error::SqrlError, Result};
7use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine};
8use std::{collections::HashMap, fmt, result, str::FromStr};
9
10const PROTOCOL_VERSION_KEY: &str = "ver";
12const NUT_KEY: &str = "nut";
13const TIF_KEY: &str = "tif";
14const QUERY_URL_KEY: &str = "qry";
15const SUCCESS_URL_KEY: &str = "url";
16const CANCEL_URL_KEY: &str = "can";
17const SECRET_INDEX_KEY: &str = "sin";
18const SERVER_UNLOCK_KEY_KEY: &str = "suk";
19const ASK_KEY: &str = "ask";
20
21#[derive(Debug, PartialEq)]
23pub struct ServerResponse {
24 pub protocol_version: ProtocolVersion,
26 pub nut: String,
28 pub transaction_indication_flags: Vec<TIFValue>,
30 pub query_url: String,
32 pub success_url: Option<String>,
35 pub cancel_url: Option<String>,
37 pub secret_index: Option<String>,
40 pub server_unlock_key: Option<String>,
42 pub ask: Option<String>,
45}
46
47impl ServerResponse {
48 pub fn new(
50 nut: String,
51 transaction_indication_flags: Vec<TIFValue>,
52 query_url: String,
53 ) -> ServerResponse {
54 ServerResponse {
55 protocol_version: ProtocolVersion::new(PROTOCOL_VERSIONS).unwrap(),
56 nut,
57 transaction_indication_flags,
58 query_url,
59 success_url: None,
60 cancel_url: None,
61 secret_index: None,
62 server_unlock_key: None,
63 ask: None,
64 }
65 }
66
67 pub fn from_base64(base64_string: &str) -> Result<Self> {
69 let server_data = String::from_utf8(BASE64_URL_SAFE_NO_PAD.decode(base64_string)?)?;
71 Self::from_str(&server_data)
72 }
73
74 pub fn to_base64(&self) -> String {
76 BASE64_URL_SAFE_NO_PAD.encode(self.to_string().as_bytes())
77 }
78}
79
80impl fmt::Display for ServerResponse {
81 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82 let mut map = HashMap::<&str, &str>::new();
83 let protocol = self.protocol_version.to_string();
84 map.insert(PROTOCOL_VERSION_KEY, &protocol);
85 map.insert(NUT_KEY, &self.nut);
86
87 let mut tif: u16 = 0;
88 for t in &self.transaction_indication_flags {
89 tif |= *t as u16;
90 }
91
92 let tif_string = tif.to_string();
93 map.insert(TIF_KEY, &tif_string);
94 map.insert(QUERY_URL_KEY, &self.query_url);
95
96 if let Some(url) = &self.success_url {
97 map.insert(SUCCESS_URL_KEY, url);
98 }
99 if let Some(can) = &self.cancel_url {
100 map.insert(CANCEL_URL_KEY, can);
101 }
102 if let Some(sin) = &self.secret_index {
103 map.insert(SECRET_INDEX_KEY, sin);
104 }
105 if let Some(suk) = &self.server_unlock_key {
106 map.insert(SERVER_UNLOCK_KEY_KEY, suk);
107 }
108 if let Some(ask) = &self.ask {
109 map.insert(ASK_KEY, ask);
110 }
111
112 write!(f, "{}", &encode_newline_data(&map))
113 }
114}
115
116impl FromStr for ServerResponse {
117 type Err = SqrlError;
118
119 fn from_str(s: &str) -> result::Result<Self, Self::Err> {
120 let data = parse_newline_data(s)?;
121
122 let ver_string = get_or_error(
124 &data,
125 PROTOCOL_VERSION_KEY,
126 "No version number in server response",
127 )?;
128 let protocol_version = ProtocolVersion::new(&ver_string)?;
129 let nut = get_or_error(&data, NUT_KEY, "No nut in server response")?;
130 let tif_string = get_or_error(&data, TIF_KEY, "No status code (tif) in server response")?;
131 let transaction_indication_flags = TIFValue::parse_str(&tif_string)?;
132
133 let query_url = get_or_error(
134 &data,
135 QUERY_URL_KEY,
136 "No query url (qry) in server response",
137 )?;
138
139 let success_url = data.get(SUCCESS_URL_KEY).map(|x| x.to_string());
141 let cancel_url = data.get(CANCEL_URL_KEY).map(|x| x.to_string());
142 let secret_index = data.get(SECRET_INDEX_KEY).map(|x| x.to_string());
143 let server_unlock_key = data.get(SERVER_UNLOCK_KEY_KEY).map(|x| x.to_string());
144 let ask = data.get(ASK_KEY).map(|x| x.to_string());
145
146 Ok(ServerResponse {
147 protocol_version,
148 nut,
149 transaction_indication_flags,
150 query_url,
151 success_url,
152 cancel_url,
153 secret_index,
154 server_unlock_key,
155 ask,
156 })
157 }
158}
159
160#[derive(Clone, Copy, Debug, PartialEq)]
162pub enum TIFValue {
163 CurrentIdMatch = 0x1,
166 PreviousIdMatch = 0x2,
169 IpsMatch = 0x4,
172 SqrlDisabled = 0x8,
174 FunctionNotSupported = 0x10,
176 TransientError = 0x20,
179 CommandFailed = 0x40,
181 ClientFailure = 0x80,
183 BadId = 0x100,
186 IdentitySuperseded = 0x200,
188}
189
190impl TIFValue {
191 pub fn parse_str(value: &str) -> Result<Vec<Self>> {
193 match value.parse::<u16>() {
194 Ok(x) => Ok(Self::from_u16(x)),
195 Err(_) => Err(SqrlError::new(format!(
196 "Unable to parse server response status code (tif): {}",
197 value
198 ))),
199 }
200 }
201
202 pub fn from_u16(value: u16) -> Vec<Self> {
204 let mut ret = Vec::new();
205
206 if value & TIFValue::CurrentIdMatch as u16 > 0 {
207 ret.push(TIFValue::CurrentIdMatch);
208 }
209 if value & TIFValue::PreviousIdMatch as u16 > 0 {
210 ret.push(TIFValue::PreviousIdMatch);
211 }
212 if value & TIFValue::IpsMatch as u16 > 0 {
213 ret.push(TIFValue::IpsMatch);
214 }
215 if value & TIFValue::SqrlDisabled as u16 > 0 {
216 ret.push(TIFValue::SqrlDisabled);
217 }
218 if value & TIFValue::FunctionNotSupported as u16 > 0 {
219 ret.push(TIFValue::FunctionNotSupported);
220 }
221 if value & TIFValue::TransientError as u16 > 0 {
222 ret.push(TIFValue::TransientError);
223 }
224 if value & TIFValue::CommandFailed as u16 > 0 {
225 ret.push(TIFValue::CommandFailed);
226 }
227 if value & TIFValue::ClientFailure as u16 > 0 {
228 ret.push(TIFValue::ClientFailure);
229 }
230 if value & TIFValue::BadId as u16 > 0 {
231 ret.push(TIFValue::BadId);
232 }
233 if value & TIFValue::IdentitySuperseded as u16 > 0 {
234 ret.push(TIFValue::IdentitySuperseded);
235 }
236
237 ret
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use rand::{distr::Alphanumeric, rng, Rng};
245
246 const TEST_SERVER_RESPONSE: &str = "dmVyPTENCm51dD0xV005bGZGMVNULXoNCnRpZj01DQpxcnk9L2NsaS5zcXJsP251dD0xV005bGZGMVNULXoNCnN1az1CTUZEbTdiUGxzUW9qdUpzb0RUdmxTMU1jbndnU2N2a3RGODR2TGpzY0drDQo";
247
248 #[test]
249 fn server_response_validate_example() {
250 let response = ServerResponse::from_base64(TEST_SERVER_RESPONSE).unwrap();
251 assert_eq!(response.protocol_version.to_string(), "1");
252 assert_eq!(response.nut, "1WM9lfF1ST-z");
253 assert_eq!(response.query_url, "/cli.sqrl?nut=1WM9lfF1ST-z");
254 assert_eq!(
255 response.server_unlock_key.unwrap(),
256 "BMFDm7bPlsQojuJsoDTvlS1McnwgScvktF84vLjscGk"
257 )
258 }
259
260 #[test]
261 fn server_response_encode_decode() {
262 let nut: String = rng()
263 .sample_iter(&Alphanumeric)
264 .take(30)
265 .map(char::from)
266 .collect();
267 let qry: String = rng()
268 .sample_iter(&Alphanumeric)
269 .take(30)
270 .map(char::from)
271 .collect();
272 let tif: u16 = rng().random_range(0..1023);
273
274 let initial_response = ServerResponse::new(nut, TIFValue::from_u16(tif), qry);
275 let decoded_response = ServerResponse::from_base64(&initial_response.to_base64()).unwrap();
276
277 assert_eq!(initial_response, decoded_response);
278 }
279
280 #[test]
281 fn tif_value_from_string() {
282 let resp = TIFValue::parse_str("674").unwrap();
283 assert_eq!(4, resp.len());
284 assert!(resp.contains(&TIFValue::PreviousIdMatch));
285 assert!(resp.contains(&TIFValue::TransientError));
286 assert!(resp.contains(&TIFValue::ClientFailure));
287 assert!(resp.contains(&TIFValue::IdentitySuperseded));
288 }
289
290 #[test]
291 fn tif_value_from_u16() {
292 let resp = TIFValue::from_u16(73);
293 assert_eq!(3, resp.len());
294 assert!(resp.contains(&TIFValue::CurrentIdMatch));
295 assert!(resp.contains(&TIFValue::SqrlDisabled));
296 assert!(resp.contains(&TIFValue::CommandFailed));
297 }
298}