mod error;
mod private_key;
use serde::Deserialize;
use std::collections::HashMap;
pub use error::Error;
pub use private_key::PrivateKey;
pub enum VerifyType {
APP,
WEB,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct JiGuangLoginToken {
_id: Option<u128>,
code: u32,
_content: String,
_ex_id: Option<String>,
phone: Option<String>,
}
#[derive(Debug, PartialEq)]
pub struct JiGuang<'r> {
app_key: &'r str,
master_secret: &'r str,
}
impl<'r> JiGuang<'r> {
pub fn new(app_key: &'r str, master_secret: &'r str) -> Self {
Self {
app_key,
master_secret,
}
}
pub async fn login_token_verify(
&self,
login_token: &str,
private_key: &PrivateKey,
) -> Result<String, Error> {
let key = match rsa::RSAPrivateKey::from_pkcs8(&private_key.key) {
Ok(key) => key,
Err(e) => return Err(Error::new(1, format!("{:?}", e))),
};
let client = reqwest::Client::new();
let mut map = HashMap::new();
map.insert("loginToken", login_token);
let jiguang_error = JiGuang::get_jiguang_err();
let jiguang_token = match client
.post("https://api.verification.jpush.cn/v1/web/loginTokenVerify")
.basic_auth(self.app_key, Some(self.master_secret))
.json(&map)
.send()
.await
{
Ok(resp) => match resp.json::<JiGuangLoginToken>().await {
Ok(token) => match token.code {
8000 => token,
error_code if jiguang_error.contains_key(&error_code) => {
return Err(Error::new(
error_code,
format!("{}", jiguang_error.get(&error_code).unwrap()),
))
}
_ => return Err(Error::new(4, String::from(""))),
},
Err(e) => return Err(Error::new(3, format!("{:?}", e))),
},
Err(e) => return Err(Error::new(2, format!("{:?}", e))),
};
let phone = match key.decrypt(
rsa::PaddingScheme::new_pkcs1v15_encrypt(),
&base64::decode(jiguang_token.phone.unwrap()).unwrap(),
) {
Ok(phone) => phone,
Err(e) => return Err(Error::new(5, format!("{:?}", e))),
};
Ok(String::from_utf8(phone).unwrap())
}
pub async fn verify(
&self,
token: &str,
phone: &str,
verify_type: VerifyType,
) -> Result<bool, Error> {
let client = reqwest::Client::new();
let mut map = HashMap::new();
map.insert("token", token);
map.insert("phone", phone);
let jiguang_error = JiGuang::get_jiguang_err();
match client
.post(match verify_type {
VerifyType::APP => "https://api.verification.jpush.cn/v1/web/verify",
VerifyType::WEB => "https://api.verification.jpush.cn/v1/web/h5/verify",
})
.basic_auth(self.app_key, Some(self.master_secret))
.json(&map)
.send()
.await
{
Ok(resp) => match resp.json::<JiGuangLoginToken>().await {
Ok(token) => match token.code {
9000 => return Ok(true),
error_code if jiguang_error.contains_key(&error_code) => {
return Err(Error::new(
error_code,
format!("{}", jiguang_error.get(&error_code).unwrap()),
))
}
_ => return Err(Error::new(4, String::from(""))),
},
Err(e) => return Err(Error::new(3, format!("{:?}", e))),
},
Err(e) => return Err(Error::new(2, format!("{:?}", e))),
}
}
fn get_jiguang_err() -> HashMap<u32, &'static str> {
let mut jiguang_error: HashMap<u32, &'static str> = HashMap::new();
jiguang_error.insert(8001, "JiGuang: get phone fail; 获取一键登录的手机号码失败");
jiguang_error.insert(9001, "JiGuang: verify not consistent; 手机号验证不一致");
jiguang_error.insert(9002, "JiGuang: unknown result; 结果未知");
jiguang_error.insert(
9003,
"JiGuang: token expired or not exist; token失效或不存在",
);
jiguang_error.insert(9004, "JiGuang: config not found; 获取配置失败");
jiguang_error.insert(
9005,
"verify interval is less than the minimum limit; 同一号码连续两次提交认证间隔过短",
);
jiguang_error.insert(
9006,
"JiGuang: frequency of verifying single number is beyond the maximum limit; 同一号码自然日内认证次数超过限制");
jiguang_error.insert(
9007,
"beyond daily frequency limit; appKey自然日认证消耗超过限制",
);
jiguang_error.insert(9010, "JiGuang: miss auth; 缺少鉴权信息");
jiguang_error.insert(9011, "JiGuang: auth failed; 鉴权失败");
jiguang_error.insert(9012, "JiGuang: parameter invalid; 参数错误");
jiguang_error.insert(
9013,
"request method not supported; 请求方式错误,请用POST请求",
);
jiguang_error.insert(9014, "JiGuang: appkey is blocked; 功能被禁用");
jiguang_error.insert(
9015,
"http media type not supported; 请检查Content type类型",
);
jiguang_error.insert(9018, "JiGuang: appKey no money; 账户余额不足");
jiguang_error.insert(
9020,
"decrypt token failed; 解密token失败,请检查appkey是否正确",
);
jiguang_error.insert(
9021,
"JiGuang: token invalid; token非法,请确认token获取正确",
);
jiguang_error.insert(
9022,
"encrypt mobile failed; 加密号码失败,请检查RSA公钥是否正确",
);
jiguang_error.insert(9031, "JiGuang: not validate user; 未开通认证服务");
jiguang_error.insert(9099, "JiGuang: bad server; 服务器未知错误");
jiguang_error.insert(9200, "JiGuang: success; 一键登录开通成功");
jiguang_error.insert(9202, "JiGuang: appKey not exists; appKey不存在");
jiguang_error.insert(9203, "JiGuang: signOnce opened already; 一键登录已开通");
jiguang_error.insert(9301, "JiGuang: signOnce opened failed; 一键登录开通失败");
jiguang_error
}
}
#[cfg(test)]
mod tests {
use super::{JiGuang, VerifyType};
use crate::PrivateKey;
#[test]
fn it_works() {
let jiguang = JiGuang::new("12345", "qwerty");
assert_eq!(
jiguang,
JiGuang {
app_key: "12345",
master_secret: "qwerty"
}
);
}
#[tokio::test]
async fn login_token() {
let s = r#"
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALx3lux8fiSk8+2f
au7sdQtaAu7GGEIr5juBy6nXq4K+73rN8HPMxEpmg6SnGMFzDL+UlUH9JoRuW7D4
qi7mHmtiOhLXbTSNpPPM/It9gHXYDMV1bD4Z6l3gafttaoim1JGfCqlXQAjzVm1u
-----END PRIVATE KEY-----"#;
let private_key = PrivateKey::from_str(s).unwrap();
let phone = JiGuang::new("12345", "qwerty")
.login_token_verify("login_token", &private_key)
.await
.unwrap();
assert_eq!(phone.as_str(), "xxxxxxxx");
}
#[tokio::test]
async fn verify() {
let res = JiGuang::new("12345", "qwerty")
.verify("token", "xxxxxxxx", VerifyType::APP)
.await
.unwrap();
assert!(res);
}
}