| mod checksum; |
| use ::base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _}; |
| pub use checksum::*; |
| mod token; |
| pub use token::*; |
| mod base64; |
| pub use base64::*; |
|
|
| use super::model::{token::TokenPayload, userinfo::{StripeProfile, TokenProfile, UsageProfile, UserProfile}}; |
| use crate::app::{ |
| constant::{COMMA, FALSE, TRUE}, |
| lazy::{TOKEN_DELIMITER, USE_COMMA_DELIMITER}, |
| }; |
|
|
| pub fn parse_bool_from_env(key: &str, default: bool) -> bool { |
| std::env::var(key) |
| .ok() |
| .map(|v| match v.to_lowercase().as_str() { |
| TRUE | "1" => true, |
| FALSE | "0" => false, |
| _ => default, |
| }) |
| .unwrap_or(default) |
| } |
|
|
| pub fn parse_string_from_env(key: &str, default: &str) -> String { |
| std::env::var(key).unwrap_or_else(|_| default.to_string()) |
| } |
|
|
| pub fn parse_ascii_char_from_env(key: &str, default: char) -> char { |
| std::env::var(key) |
| .ok() |
| .and_then(|v| { |
| let chars: Vec<char> = v.chars().collect(); |
| if chars.len() == 1 && chars[0].is_ascii() { |
| Some(chars[0]) |
| } else { |
| None |
| } |
| }) |
| .unwrap_or(default) |
| } |
|
|
| pub fn parse_usize_from_env(key: &str, default: usize) -> usize { |
| std::env::var(key) |
| .ok() |
| .and_then(|v| v.parse().ok()) |
| .unwrap_or(default) |
| } |
|
|
| pub trait TrimNewlines { |
| fn trim_leading_newlines(self) -> Self; |
| } |
|
|
| impl TrimNewlines for String { |
| #[inline(always)] |
| fn trim_leading_newlines(mut self) -> Self { |
| let bytes = self.as_bytes(); |
| if bytes.len() >= 2 && bytes[0] == b'\n' && bytes[1] == b'\n' { |
| unsafe { |
| let start_ptr = self.as_mut_ptr(); |
| let new_len = self.len() - 2; |
| std::ptr::copy(start_ptr.add(2), start_ptr, new_len); |
| self.as_mut_vec().set_len(new_len); |
| } |
| } |
| self |
| } |
| } |
|
|
| pub async fn get_token_profile(auth_token: &str) -> Option<TokenProfile> { |
| let user_id = extract_user_id(auth_token)?; |
|
|
| |
| let client = super::client::build_usage_client(&user_id, auth_token); |
|
|
| |
| |
| |
| |
| |
| let usage = client |
| .send() |
| .await |
| .ok()? |
| .json::<UsageProfile>() |
| .await |
| .ok()?; |
|
|
| let user = get_user_profile(auth_token).await?; |
|
|
| |
| let stripe = get_stripe_profile(auth_token).await?; |
|
|
| |
| Some(TokenProfile { |
| usage, |
| user, |
| stripe, |
| }) |
| } |
|
|
| pub async fn get_stripe_profile(auth_token: &str) -> Option<StripeProfile> { |
| let client = super::client::build_profile_client(auth_token); |
| let response = client |
| .send() |
| .await |
| .ok()? |
| .json::<StripeProfile>() |
| .await |
| .ok()?; |
| Some(response) |
| } |
|
|
| pub async fn get_user_profile(auth_token: &str) -> Option<UserProfile> { |
| let user_id = extract_user_id(auth_token)?; |
|
|
| |
| let client = super::client::build_userinfo_client(&user_id, auth_token); |
|
|
| |
| let user_profile = client.send().await.ok()?.json::<UserProfile>().await.ok()?; |
|
|
| Some(user_profile) |
| } |
|
|
| pub fn validate_token_and_checksum(auth_token: &str) -> Option<(String, String)> { |
| |
| let mut delimiter_pos = auth_token.rfind(*TOKEN_DELIMITER); |
|
|
| |
| if delimiter_pos.is_none() && *USE_COMMA_DELIMITER { |
| delimiter_pos = auth_token.rfind(COMMA); |
| } |
|
|
| |
| let comma_pos = delimiter_pos?; |
|
|
| |
| let (token_part, checksum) = auth_token.split_at(comma_pos); |
| let checksum = &checksum[1..]; |
|
|
| |
| let colon_pos = token_part.rfind(':'); |
| let encoded_colon_pos = token_part.rfind("%3A"); |
|
|
| let token = match (colon_pos, encoded_colon_pos) { |
| (None, None) => token_part, |
| (Some(pos1), None) => &token_part[(pos1 + 1)..], |
| (None, Some(pos2)) => &token_part[(pos2 + 3)..], |
| (Some(pos1), Some(pos2)) => { |
| let pos = pos1.max(pos2); |
| let start = if pos == pos2 { pos + 3 } else { pos + 1 }; |
| &token_part[start..] |
| } |
| }; |
|
|
| |
| if validate_token(token) && validate_checksum(checksum) { |
| Some((token.to_string(), generate_checksum_with_repair(checksum))) |
| } else { |
| None |
| } |
| } |
|
|
| pub fn extract_token(auth_token: &str) -> Option<String> { |
| |
| let mut delimiter_pos = auth_token.rfind(*TOKEN_DELIMITER); |
|
|
| |
| if delimiter_pos.is_none() && *USE_COMMA_DELIMITER { |
| delimiter_pos = auth_token.rfind(COMMA); |
| } |
|
|
| |
| let token_part = match delimiter_pos { |
| Some(pos) => &auth_token[..pos], |
| None => auth_token, |
| }; |
|
|
| |
| let colon_pos = token_part.rfind(':'); |
| let encoded_colon_pos = token_part.rfind("%3A"); |
|
|
| let token = match (colon_pos, encoded_colon_pos) { |
| (None, None) => token_part, |
| (Some(pos1), None) => &token_part[(pos1 + 1)..], |
| (None, Some(pos2)) => &token_part[(pos2 + 3)..], |
| (Some(pos1), Some(pos2)) => { |
| let pos = pos1.max(pos2); |
| let start = if pos == pos2 { pos + 3 } else { pos + 1 }; |
| &token_part[start..] |
| } |
| }; |
|
|
| |
| if validate_token(token) { |
| Some(token.to_string()) |
| } else { |
| None |
| } |
| } |
|
|
| pub fn format_time_ms(seconds: f64) -> f64 { |
| (seconds * 1000.0).round() / 1000.0 |
| } |
|
|
| use crate::chat::config::key_config; |
|
|
| |
| pub fn token_to_tokeninfo(auth_token: &str) -> Option<key_config::TokenInfo> { |
| let (token, checksum) = validate_token_and_checksum(auth_token)?; |
|
|
| |
| let parts: Vec<&str> = token.split('.').collect(); |
| if parts.len() != 3 { |
| return None; |
| } |
|
|
| |
| let payload = match URL_SAFE_NO_PAD.decode(parts[1]) { |
| Ok(decoded) => decoded, |
| Err(_) => return None, |
| }; |
|
|
| |
| let payload_str = match String::from_utf8(payload) { |
| Ok(s) => s, |
| Err(_) => return None, |
| }; |
|
|
| |
| let payload: TokenPayload = match serde_json::from_str(&payload_str) { |
| Ok(p) => p, |
| Err(_) => return None, |
| }; |
|
|
| let (machine_id_hash, mac_id_hash) = extract_hashes(&checksum)?; |
|
|
| |
| Some(key_config::TokenInfo { |
| sub: payload.sub, |
| exp: payload.exp, |
| randomness: payload.randomness, |
| signature: parts[2].to_string(), |
| machine_id: machine_id_hash, |
| mac_id: mac_id_hash, |
| }) |
| } |
|
|
| |
| pub fn tokeninfo_to_token(info: &key_config::TokenInfo) -> Option<(String, String)> { |
| |
| let payload = TokenPayload { |
| sub: info.sub.clone(), |
| exp: info.exp, |
| randomness: info.randomness.clone(), |
| time: (info.exp - 2592000000).to_string(), |
| iss: ISSUER.to_string(), |
| scope: SCOPE.to_string(), |
| aud: AUDIENCE.to_string(), |
| }; |
|
|
| let payload_str = match serde_json::to_string(&payload) { |
| Ok(s) => s, |
| Err(_) => return None, |
| }; |
|
|
| let payload_b64 = URL_SAFE_NO_PAD.encode(payload_str.as_bytes()); |
|
|
| |
| let device_id = hex::encode(&info.machine_id); |
| let mac_addr = if !info.mac_id.is_empty() { |
| Some(hex::encode(&info.mac_id)) |
| } else { |
| None |
| }; |
|
|
| |
| Some((format!("{}.{}.{}", HEADER_B64, payload_b64, info.signature), generate_checksum(&device_id, mac_addr.as_deref()))) |
| } |
|
|