1use phonenumber::PhoneNumber;
2use reqwest::header::HeaderName;
3use reqwest::RequestBuilder;
4
5use crate::error::{ApiResult, AuthyErr};
6
7
8pub struct Client {
9 http: reqwest::Client,
10 api_key: String
11}
12
13pub enum Via {
14 SMS,
15 Call
16}
17
18impl Into<&str> for Via {
19 fn into(self) -> &'static str {
20 match self {
21 Via::SMS => "sms",
22 Via::Call => "call"
23 }
24 }
25}
26
27
28#[derive(Serialize, Debug)]
29struct VerifyRequest<'a> {
30 phone_number: &'a str,
31 country_code: &'a str,
32 via: &'a str,
33 code_length: u8,
34 locale: &'a str
35}
36
37#[derive(Deserialize, Debug)]
38pub struct VerifyResponse {
39 carrier: String,
40 is_cellphone: bool,
41 message: String,
42 seconds_to_expire: u32,
43 uuid: String,
44 success: bool
45}
46
47#[derive(Serialize, Debug)]
48pub struct CheckRequest<'a> {
49 phone_number: &'a str,
50 country_code: &'a str,
51 verification_code: u32
52}
53
54#[derive(Deserialize, Debug)]
55pub struct CheckResponse {
56 message: String,
57 success: bool
58}
59
60impl Into<bool> for CheckResponse {
61 fn into(self) -> bool {
62 self.success
63 }
64}
65
66#[derive(Serialize, Debug)]
67pub struct StatusRequest<'a> {
68 uuid: &'a str
69}
70
71enum Status {
72 Expired,
73 Verified,
74 Pending,
75 Unknown
76}
77
78impl<'a> From<&'a str> for Status {
79 fn from(value: &'a str) -> Status {
80 match value {
81 "expired" => Status::Expired,
82 "verified" => Status::Verified,
83 "pending" => Status::Pending,
84 _ => Status::Unknown
85 }
86 }
87}
88
89#[derive(Deserialize, Debug)]
90pub struct StatusResponse {
91 status: String,
92 seconds_to_expire: u32,
93 success: bool,
94 message: String
95}
96
97
98
99impl Client {
100 pub fn new(api_key: &str) -> Client {
101 Client {
102 http: reqwest::Client::new(),
103 api_key: api_key.into()
104 }
105 }
106
107 fn query<T, S>(&self, rb: RequestBuilder, data: &T) -> Result<S, AuthyErr>
108 where T: serde::Serialize,
109 for<'de> S: serde::Deserialize<'de>
110 {
111 let x_authy_api_key: HeaderName = HeaderName::from_static("x-authy-api-key");
112
113 rb.header(x_authy_api_key, self.api_key.clone())
114 .form(data)
115 .send()
116 .map_err(AuthyErr::Http)
117 .and_then(|mut r: reqwest::Response| {
118 r.json()
119 .map_err(AuthyErr::Http)
120 .and_then(|r: ApiResult<S>| match r {
121 ApiResult::Ok(x) => Ok(x),
122 ApiResult::Err(x) => Err(x)
123 }.map_err(AuthyErr::Api))
124 })
125 }
126
127 pub fn verify(&self, number: &PhoneNumber, via: Via, code_length: u8, locale: &str) -> Result<VerifyResponse, AuthyErr> {
128 self.query::<VerifyRequest, VerifyResponse>(
129 self.http.post("https://api.authy.com/protected/json/phones/verification/start"),
130 &VerifyRequest {
131 phone_number: &number.national().to_string(),
132 country_code: &number.code().value().to_string(),
133 via: via.into(),
134 code_length,
135 locale,
136 })
137 }
138
139 pub fn check(&self, number: &PhoneNumber, verification_code: u32) -> Result<CheckResponse, AuthyErr> {
140 self.query::<CheckRequest, CheckResponse>(
141 self.http.get("https://api.authy.com/protected/json/phones/verification/check"),
142 &CheckRequest {
143 phone_number: &number.national().to_string(),
144 country_code: &number.code().value().to_string(),
145 verification_code
146 })
147 }
148
149 pub fn status(&self, uuid: &str) -> Result<StatusResponse, AuthyErr> {
150 self.query::<StatusRequest, StatusResponse>(
151 self.http.get("https://api.authy.com/protected/json/phones/verification/status"),
152 &StatusRequest { uuid }
153 )
154 }
155}
156
157
158#[cfg(test)]
159mod tests {
160 use crate::client::{Client, Via};
161 use phonenumber::PhoneNumber;
162
163 fn setup() -> (Client, PhoneNumber) {
164 dotenv::dotenv()
165 .expect("No .env file found");
166 (Client::new(&dotenv::var("VERIFY_API_KEY")
167 .expect("No verify API key in .env file")),
168 phonenumber::parse(None, dotenv::var("VERIFY_TEST_PHONE_NUMBER")
169 .expect("No verify test phone number in .env file"))
170 .expect("Can't parse test phone number"))
171 }
172
173
174 #[test]
175 fn test_verify() {
176 let (client, test_phone_number) = setup();
177 println!("Response:\n{:#?}", client.verify(&test_phone_number, Via::SMS, 6, "de")
178 .expect("verify error"));
179 }
180
181 #[test]
182 fn test_check() {
183 let verification_code: Option<u32> = None;
184 let verification_code = verification_code.expect("You must set the verification code here to get a positive result");
185
186 let (client, test_phone_number) = setup();
187 println!("Response:\n{:#?}", client.check(&test_phone_number, verification_code)
188 .expect("check error"));
189 }
190
191 #[test]
192 fn test_status() {
193 let uuid: Option<&str> = None;
194 let uuid = uuid.expect("You must set the UUID for a verification request here to get a positive result");
195
196 let (client, _) = setup();
197 println!("Response:\n{:#?}", client.status(uuid));
198 }
199}