implement auto reauth

This commit is contained in:
lemonsh 2023-04-30 19:43:34 +02:00
parent 6ad2534983
commit 7838ca1978
3 changed files with 63 additions and 33 deletions

View File

@ -11,11 +11,11 @@ async fn main() -> Result<()> {
let ip = args.next().expect("no ip specified"); let ip = args.next().expect("no ip specified");
let code = args.next().expect("no code specified"); let code = args.next().expect("no code specified");
let connect_box = ConnectBox::new(ip)?; let connect_box = ConnectBox::new(ip, code)?;
connect_box.login(&code).await?; connect_box.login().await?;
let devices = connect_box.get_devices().await?; let devices = connect_box.get_devices().await?;
println!("{devices:?}"); println!("{devices:#?}");
Ok(()) Ok(())
} }

View File

@ -9,6 +9,8 @@ pub enum Error {
IncorrectCode, IncorrectCode,
#[error("unexpected response from the server: {0:?}")] #[error("unexpected response from the server: {0:?}")]
UnexpectedResponse(String), UnexpectedResponse(String),
#[error("you are not logged in, or perhaps the session has expired")]
NotAuthorized,
#[error(transparent)] #[error(transparent)]
URLParseError(#[from] url::ParseError), URLParseError(#[from] url::ParseError),

View File

@ -21,14 +21,16 @@ type Field<'a, 'b> = (Cow<'a, str>, Cow<'b, str>);
/// The entry point of the library - the API client /// The entry point of the library - the API client
pub struct ConnectBox { pub struct ConnectBox {
http: Client, http: Client,
code: String,
cookie_store: Arc<Jar>, cookie_store: Arc<Jar>,
base_url: Url, base_url: Url,
getter_url: Url, getter_url: Url,
setter_url: Url, setter_url: Url,
auto_reauth: bool
} }
impl ConnectBox { impl ConnectBox {
pub fn new(address: impl Display) -> Result<Self> { pub fn new(address: impl Display, code: String) -> Result<Self> {
let cookie_store = Arc::new(Jar::default()); let cookie_store = Arc::new(Jar::default());
let http = Client::builder() let http = Client::builder()
.user_agent("Mozilla/5.0") .user_agent("Mozilla/5.0")
@ -44,6 +46,8 @@ impl ConnectBox {
base_url, base_url,
getter_url, getter_url,
setter_url, setter_url,
code,
auto_reauth: false
}) })
} }
@ -64,46 +68,60 @@ impl ConnectBox {
} }
async fn xml_getter<T: DeserializeOwned>(&self, function: u32) -> Result<T> { async fn xml_getter<T: DeserializeOwned>(&self, function: u32) -> Result<T> {
let session_token = self.cookie("sessionToken")?.ok_or(Error::NoSessionToken)?; let mut reauthed = false;
let form: Vec<Field> = vec![ loop {
("token".into(), session_token.into()), let session_token = self.cookie("sessionToken")?.ok_or(Error::NoSessionToken)?;
("fun".into(), function.to_string().into()), let form: Vec<Field> = vec![
]; ("token".into(), session_token.into()),
let req = self.http.post(self.getter_url.clone()).form(&form); ("fun".into(), function.to_string().into()),
let resp = req.send().await?.text().await?; ];
let obj = quick_xml::de::from_str(&resp)?; let req = self.http.post(self.getter_url.clone()).form(&form);
Ok(obj) let resp = req.send().await?;
if resp.status().is_redirection() {
if self.auto_reauth && !reauthed {
reauthed = true;
continue;
}
return Err(Error::NotAuthorized)
}
return Ok(quick_xml::de::from_str(&resp.text().await?)?);
}
} }
async fn xml_setter( async fn xml_setter(
&self, &self,
function: u32, function: u32,
fields: Option<impl IntoIterator<Item = Field<'_, '_>>>, fields: Option<impl AsRef<[Field<'_, '_>]>>,
) -> Result<String> { ) -> Result<String> {
let session_token = self.cookie("sessionToken")?.ok_or(Error::NoSessionToken)?; let mut reauthed = false;
let mut form = vec![ loop {
("token".into(), session_token.into()), let session_token = self.cookie("sessionToken")?.ok_or(Error::NoSessionToken)?;
("fun".into(), function.to_string().into()), let mut form: Vec<(Cow<str>, Cow<str>)> = vec![
]; ("token".into(), session_token.into()),
if let Some(fields) = fields { ("fun".into(), function.to_string().into()),
form.extend(fields); ];
if let Some(fields) = &fields {
for (key, value) in fields.as_ref() {
form.push((key.clone(), value.clone()));
}
}
let req = self.http.post(self.setter_url.clone()).form(&form);
let resp = req.send().await?;
if resp.status().is_redirection() {
if self.auto_reauth && !reauthed {
reauthed = true;
continue;
}
return Err(Error::NotAuthorized)
}
return Ok(resp.text().await?);
} }
let req = self.http.post(self.setter_url.clone()).form(&form);
let resp = req.send().await?;
Ok(resp.text().await?)
} }
pub async fn login(&self, code: impl AsRef<str>) -> Result<()> { async fn _login(&self) -> Result<()> {
// get the session cookie
self.http
.get(self.base_url.join("common_page/login.html")?)
.send()
.await?;
// log in
let fields = vec![ let fields = vec![
("Username".into(), "NULL".into()), ("Username".into(), "NULL".into()),
("Password".into(), code.as_ref().into()), ("Password".into(), (&self.code).into()),
]; ];
let response = self.xml_setter(functions::LOGIN, Some(fields)).await?; let response = self.xml_setter(functions::LOGIN, Some(fields)).await?;
if response == "idloginincorrect" { if response == "idloginincorrect" {
@ -118,6 +136,16 @@ impl ConnectBox {
Ok(()) Ok(())
} }
pub async fn login(&self) -> Result<()> {
// get the session cookie
self.http
.get(self.base_url.join("common_page/login.html")?)
.send()
.await?;
self._login().await
}
pub async fn get_devices(&self) -> Result<models::LanUserTable> { pub async fn get_devices(&self) -> Result<models::LanUserTable> {
self.xml_getter(functions::LAN_TABLE).await self.xml_getter(functions::LAN_TABLE).await
} }