mod login;
mod logout;
pub use login::login;
pub use logout::logout;
use url::Url;
use std::{
fs::{self, File, OpenOptions},
io::{Read, Write},
path::PathBuf,
};
use base64::{engine::general_purpose, Engine};
use serde::{Deserialize, Serialize};
use crate::{
error::{Error, Result},
helper::get_eunomia_home,
};
const AUTH_FILE: &str = "eunomia_auth.json";
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct LoginInfo {
url: String,
auth: String,
}
impl LoginInfo {
pub fn new(url: &str, user: &str, pwd: &str) -> Self {
Self {
url: String::from(url),
auth: general_purpose::STANDARD.encode(format!("{}:{}", user, pwd)),
}
}
fn get_user_pwd(&self) -> Result<(String, String)> {
let dec = general_purpose::STANDARD
.decode(&self.auth)
.map_err(|e| Error::Serialize(e.to_string()))?;
let Some(idx) = dec.iter().position(|x|*x==b':') else {
return Err(Error::Serialize("auth info format incorrect".to_string()))
};
let (user, pwd) = dec.split_at(idx);
Ok((
String::from_utf8_lossy(user).to_string(),
String::from_utf8_lossy(&pwd[1..]).to_string(),
))
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct AuthInfo(Vec<LoginInfo>);
impl AuthInfo {
pub fn get() -> Result<Self> {
AuthInfo::read_from_file(&mut get_auth_save_file()?).map_err(|e| {
if matches!(e, Error::Serialize(_)) {
Error::Serialize(
"serialize auth config file fail, maybe the file corrupt".to_string(),
)
} else {
e
}
})
}
fn read_from_file(file: &mut File) -> Result<AuthInfo> {
let mut data = vec![];
file.read_to_end(&mut data).map_err(Error::IOErr)?;
if data.is_empty() {
return Ok(Self(vec![]));
}
serde_json::from_slice(&data).map_err(|e| Error::Serialize(e.to_string()))
}
fn write_to_file(&self, file: &mut File) -> Result<()> {
file.set_len(0).map_err(Error::IOErr)?;
file.write_all(
serde_json::to_vec(self)
.map_err(|e| Error::Serialize(e.to_string()))?
.as_ref(),
)
.map_err(Error::IOErr)
}
fn get_auth_info_by_url(&self, url: &str) -> Result<(String, String)> {
for i in self.0.iter() {
if i.url == url {
return i.get_user_pwd();
}
}
Err(Error::LoginInfoNotFound(
"url have no login info".to_string(),
))
}
pub fn set_login_info(&mut self, login_info: LoginInfo) {
if let Some(idx) = self.0.iter().position(|x| x.url == login_info.url) {
let _ = std::mem::replace(&mut self.0[idx], login_info);
} else {
self.0.push(login_info);
}
}
pub fn remove_login_info(&mut self, url: &str) -> Result<()> {
let Some(idx) = self.0.iter().position(|x|x.url==url) else {
return Err(Error::InvalidParam(format!("auth info of url: {} not found",url)));
};
self.0.remove(idx);
Ok(())
}
}
pub fn get_auth_info_by_url(url: &Url) -> Result<(String, String)> {
if !url.username().is_empty() {
return Ok((
url.username().into(),
url.password().unwrap_or_default().into(),
));
}
let auth_info = AuthInfo::get()?;
auth_info.get_auth_info_by_url(url.host_str().unwrap())
}
fn get_auth_save_file() -> Result<File> {
let home_dir = get_eunomia_home().map_err(|e| Error::Other(e.to_string()))?;
let mut path = PathBuf::from(home_dir);
if !path.exists() {
fs::create_dir_all(&path).map_err(Error::IOErr)?;
}
path.push(AUTH_FILE);
OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(path)
.map_err(Error::IOErr)
}
#[cfg(test)]
mod test {
use url::Url;
use super::{AuthInfo, LoginInfo};
const HOST1: &str = "http://127.0.0.1";
const USERNAME1: &str = "username_test1";
const PASSWORD1: &str = "password_test1";
const HOST2: &str = "http://172.17.0.1";
const USERNAME2: &str = "username_test2";
const PASSWORD2: &str = "password_test2";
#[test]
fn test_auth() {
let url1 = Url::parse(HOST1).unwrap();
let url2 = Url::parse(HOST2).unwrap();
let login1 = LoginInfo::new(url1.host_str().unwrap(), USERNAME1, PASSWORD1);
let login2 = LoginInfo::new(url2.host_str().unwrap(), USERNAME2, PASSWORD2);
let mut auth = AuthInfo(vec![]);
auth.set_login_info(login1.clone());
auth.set_login_info(login2.clone());
assert_eq!(auth.0.len(), 2);
assert_eq!(
auth.get_auth_info_by_url(url2.host_str().unwrap()).unwrap(),
login2.get_user_pwd().unwrap()
);
assert_eq!(
auth.get_auth_info_by_url(url1.host_str().unwrap()).unwrap(),
login1.get_user_pwd().unwrap()
);
auth.remove_login_info(url2.host_str().unwrap()).unwrap();
assert_eq!(auth.0.len(), 1);
assert_eq!(
auth.get_auth_info_by_url(url1.host_str().unwrap()).unwrap(),
login1.get_user_pwd().unwrap()
);
assert!(auth.get_auth_info_by_url(url2.host_str().unwrap()).is_err());
}
}