1extern crate chrono;
2extern crate frank_jwt as jwt;
3extern crate openssl;
4extern crate reqwest;
5extern crate uuid;
6
7pub mod crypto;
8pub mod error;
9
10use chrono::Utc;
11use error::CivicError;
12use hmac::{Hmac, Mac};
13use reqwest::header::{ACCEPT, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
14use reqwest::StatusCode;
15use serde::Deserialize;
16use serde_json::{json, Value as JsonValue};
17use sha2::Sha256;
18use uuid::Uuid;
19
20type HmacSha256 = Hmac<Sha256>;
22
23const CIVIC_SIP_API_URL: &str = "https://api.civic.com/sip";
24const CIVIC_SIP_API_PUB: &str = "049a45998638cfb3c4b211d72030d9ae8329a242db63bfb0076a54e7647370a8ac5708b57af6065805d5a6be72332620932dbb35e8d318fce18e7c980a0eb26aa1";
25const CIVIC_JWT_EXPIRATION: i64 = 180; #[derive(Deserialize)]
28struct Payload {
29 data: String,
30}
31
32pub struct CivicSipConfig {
34 pub app_id: &'static str,
35 pub app_secret: &'static str,
36 pub private_key: &'static str,
37 pub proxy: Option<&'static str>,
38}
39
40pub enum CivicEnv {
41 Prod,
42 Dev,
43}
44
45pub struct CivicSip {
46 config: CivicSipConfig,
47 env: &'static str,
48}
49
50impl CivicSip {
51 pub fn new(config: CivicSipConfig, civic_env: Option<CivicEnv>) -> CivicSip {
52 let env = match civic_env {
53 None => "prod",
54 Some(civic_env_enum) => match civic_env_enum {
55 CivicEnv::Prod => "prod",
56 CivicEnv::Dev => "dev",
57 },
58 };
59 CivicSip { config, env }
60 }
61
62 pub fn exchange_code(&self, jwt_token: &str) -> Result<JsonValue, CivicError> {
68 let body: String = json!({
69 "authToken": jwt_token,
70 "processPayload": true,
71 })
72 .to_string();
73 let auth_header: String = self.make_authorization_header(&body);
74
75 let client = match self.config.proxy {
76 Some(proxy) => reqwest::Client::builder()
77 .proxy(reqwest::Proxy::all(proxy)?)
78 .build()?,
79 _ => reqwest::Client::new(),
80 };
81
82 let mut response = client
83 .post(format!("{}/{}/scopeRequest/authCode", CIVIC_SIP_API_URL, &self.env).as_str())
84 .header(AUTHORIZATION, auth_header)
85 .header(CONTENT_LENGTH, body.len())
86 .header(CONTENT_TYPE, "application/json")
87 .header(ACCEPT, "Accept")
88 .body(body)
89 .send()?;
90
91 match response.status() {
92 StatusCode::OK => self.process_payload(response.json()?),
93
94 StatusCode::BAD_REQUEST => Err(CivicError {
95 code: 100_400,
96 message: String::from("Exchange code failed!"),
97 }),
98 StatusCode::UNAUTHORIZED => Err(CivicError {
99 code: 100_401,
100 message: String::from("Exchange code failed!"),
101 }),
102 StatusCode::FORBIDDEN => Err(CivicError {
103 code: 100_403,
104 message: String::from("Exchange code failed!"),
105 }),
106 StatusCode::NOT_FOUND => Err(CivicError {
107 code: 100_404,
108 message: String::from("Exchange code failed!"),
109 }),
110 StatusCode::INTERNAL_SERVER_ERROR => Err(CivicError {
111 code: 100_500,
112 message: String::from("Exchange code failed!"),
113 }),
114
115 status => Err(CivicError {
116 code: 1,
117 message: format!("Backend status code not supported: {:?}", status),
118 }),
119 }
120 }
121
122 fn make_authorization_header(&self, body: &str) -> String {
130 let payload = json!({
131 "jti": Uuid::new_v4(),
132 "iat": Utc::now().timestamp_millis() / 1000,
133 "exp": (Utc::now().timestamp_millis() + CIVIC_JWT_EXPIRATION) / 1000,
134 "iss": self.config.app_id,
135 "aud": CIVIC_SIP_API_URL,
136 "sub": self.config.app_id,
137 "data": {
138 "method": "POST",
139 "path": "scopeRequest/authCode",
140 },
141 });
142 let header = json!({
143 "alg": "ES256",
144 "typ": "JWT"
145 });
146
147 let private_key: Vec<u8> = crypto::get_private_key_pem(&self.config.private_key);
148 let jwt_token =
149 match frank_jwt::encode(header, &private_key, &payload, frank_jwt::Algorithm::ES256) {
150 Ok(token) => token,
151 Err(error) => {
152 panic!("There was a problem during JWT ENCODE: {:?}", error);
153 }
154 };
155
156 let mut mac = HmacSha256::new_varkey(self.config.app_secret.as_bytes()).unwrap();
158 mac.input(body.as_bytes());
159
160 return format!(
161 "Civic {}.{}",
162 jwt_token,
163 base64::encode(&mac.result().code().to_owned())
164 );
165 }
166
167 fn process_payload(&self, payload: Payload) -> Result<JsonValue, CivicError> {
170 match crypto::decode(&payload.data, CIVIC_SIP_API_PUB) {
171 Err(error) => Err(error),
172 Ok((_, jwt_payload)) => crypto::decrypt(
173 jwt_payload["data"].as_str().unwrap(),
174 &self.config.app_secret,
175 ),
176 }
177 }
178}