1pub mod licensegate_config;
2
3use base64::{Engine, engine::general_purpose};
4use licensegate_config::LicenseGateConfig;
5use reqwest::Client;
6use std::sync::Arc;
7use thiserror::Error;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum ValidationType {
11 Valid,
12 NotFound,
13 NotActive,
14 Expired,
15 LicenseScopeFailed,
16 IpLimitExceeded,
17 RateLimitExceeded,
18 FailedChallenge,
19 ServerError,
20 ConnectionError,
21}
22
23#[derive(Debug, Error)]
24pub enum LicenseGateError {
25 #[error("server error")]
26 ServerError,
27 #[error("connection error")]
28 ConnectionError,
29 #[error("challenge verification failed")]
30 ChallengeFailed,
31 #[error("validation result: {0:?}")]
32 Validation(ValidationType),
33}
34
35pub struct LicenseGate {
36 user_id: String,
37 public_rsa_key: Option<String>,
38 validation_server: String,
39 use_challenge: bool,
40 debug_mode: bool,
41 client: Arc<Client>,
42}
43
44impl LicenseGate {
45 pub fn new(user_id: impl Into<String>) -> Self {
46 Self {
47 user_id: user_id.into(),
48 public_rsa_key: None,
49 validation_server: "https://api.licensegate.io".into(),
50 use_challenge: false,
51 debug_mode: false,
52 client: Arc::new(Client::new()),
53 }
54 }
55
56 pub fn set_public_rsa_key(mut self, key: impl Into<String>) -> Self {
57 self.public_rsa_key = Some(key.into());
58 self.use_challenge = true;
59 self
60 }
61
62 pub fn set_validation_server(mut self, server: impl Into<String>) -> Self {
63 self.validation_server = server.into();
64 self
65 }
66
67 pub fn use_challenges(mut self) -> Self {
68 self.use_challenge = true;
69 self
70 }
71
72 pub fn debug(mut self) -> Self {
73 self.debug_mode = true;
74 self
75 }
76
77 pub async fn verify(
78 &self,
79 config: LicenseGateConfig,
80 ) -> Result<ValidationType, LicenseGateError> {
81 let challenge = if self.use_challenge {
82 Some(format!("{}", chrono::Utc::now().timestamp_millis()))
83 } else {
84 None
85 };
86
87 let url = self.build_url(
88 &config.license,
89 config.scope,
90 config.metadata,
91 challenge.as_deref(),
92 );
93
94 let res = self
95 .client
96 .get(&url)
97 .header("User-Agent", "RohitSangwan/LicenseGate-Rust")
98 .send()
99 .await
100 .map_err(|_| LicenseGateError::ConnectionError)?;
101
102 let status = res.status();
103 let data: serde_json::Value = res
104 .json()
105 .await
106 .map_err(|_| LicenseGateError::ServerError)?;
107
108 if self.debug_mode {
109 println!("\nRequest URL: {}", url);
110 println!("Status: {}", status);
111 println!("Response: {}", data);
112 }
113
114 if let Some(error) = data.get("error") {
115 if self.debug_mode {
116 println!("Error: {}", error);
117 }
118 return Err(LicenseGateError::ServerError);
119 }
120
121 let valid = data.get("valid").and_then(|v| v.as_bool()).unwrap_or(true);
122 let result_str = data
123 .get("result")
124 .and_then(|r| r.as_str())
125 .unwrap_or("SERVER_ERROR");
126
127 let result = match result_str {
128 "VALID" if !valid => ValidationType::ServerError,
129 "VALID" => ValidationType::Valid,
130 "NOT_FOUND" => ValidationType::NotFound,
131 "NOT_ACTIVE" => ValidationType::NotActive,
132 "EXPIRED" => ValidationType::Expired,
133 "LICENSE_SCOPE_FAILED" => ValidationType::LicenseScopeFailed,
134 "IP_LIMIT_EXCEEDED" => ValidationType::IpLimitExceeded,
135 "RATE_LIMIT_EXCEEDED" => ValidationType::RateLimitExceeded,
136 _ => ValidationType::ServerError,
137 };
138
139 if !valid {
140 return Err(LicenseGateError::Validation(result));
141 }
142
143 if self.use_challenge {
144 if let Some(signed) = data.get("signedChallenge").and_then(|s| s.as_str()) {
145 if !self.verify_challenge(challenge.unwrap().as_bytes(), signed) {
146 return Err(LicenseGateError::ChallengeFailed);
147 }
148 } else {
149 return Err(LicenseGateError::ChallengeFailed);
150 }
151 }
152
153 Ok(result)
154 }
155
156 pub async fn verify_simple(&self, config: LicenseGateConfig) -> bool {
157 matches!(self.verify(config).await, Ok(ValidationType::Valid))
158 }
159
160 fn build_url(
161 &self,
162 license_key: &str,
163 scope: Option<String>,
164 metadata: Option<String>,
165 challenge: Option<&str>,
166 ) -> String {
167 let mut query = vec![];
168 if let Some(meta) = metadata {
169 query.push(format!("metadata={}", urlencoding::encode(&meta)));
170 }
171 if let Some(sc) = scope {
172 query.push(format!("scope={}", urlencoding::encode(&sc)));
173 }
174 if let Some(ch) = challenge {
175 query.push(format!("challenge={}", urlencoding::encode(ch)));
176 }
177 let query_str = if query.is_empty() {
178 "".into()
179 } else {
180 format!("?{}", query.join("&"))
181 };
182 format!(
183 "{}/license/{}/{}/verify{}",
184 self.validation_server, self.user_id, license_key, query_str
185 )
186 }
187
188 fn verify_challenge(&self, challenge: &[u8], signed_base64: &str) -> bool {
189 use openssl::hash::MessageDigest;
190 use openssl::rsa::Rsa;
191 use openssl::sign::Verifier;
192
193 if let Some(pub_key_pem) = &self.public_rsa_key {
194 let pub_key_pem = Self::normalize_pem_format(pub_key_pem);
195 match Rsa::public_key_from_pem(pub_key_pem.as_bytes()) {
196 Ok(rsa) => {
197 let pub_key = rsa.public_key_to_pem().unwrap();
198 let pkey = openssl::pkey::PKey::public_key_from_pem(&pub_key).unwrap();
199 let mut verifier = Verifier::new(MessageDigest::sha256(), &pkey).unwrap();
200 verifier.update(challenge).unwrap();
201 if let Ok(sig) = general_purpose::STANDARD.decode(signed_base64) {
202 return verifier.verify(&sig).unwrap_or(false);
203 }
204 }
205 Err(e) => {
206 if self.debug_mode {
207 eprintln!("RSA decode error: {}", e);
208 }
209 }
210 }
211 }
212
213 false
214 }
215
216 fn normalize_pem_format(key: &str) -> String {
217 let key = key.replace("\\n", "\n").trim().to_string();
218 if key.contains("-----BEGIN") {
219 println!("Formatting key");
220 key.replace("-----BEGIN PUBLIC KEY-----", "-----BEGIN PUBLIC KEY-----\n")
221 .replace("-----END PUBLIC KEY-----", "\n-----END PUBLIC KEY-----")
222 } else {
223 key
224 }
225 }
226}