1use lazy_static::lazy_static;
2use log::{debug, info};
3use regex::Regex;
4use serde::Serialize;
5use thiserror::Error;
6
7use crate::fetchable::Fetchable;
8
9#[derive(Error, Debug)]
10pub enum FetchError {
11 #[error("request error: {0}")]
12 Reqwest(#[from] reqwest::Error),
13 #[error("cannot find the token")]
14 Token,
15}
16
17#[derive(Debug, Clone)]
18pub struct Celcat {
19 client: reqwest::Client,
20 address: String,
21 token: String,
22}
23
24impl Celcat {
25 pub async fn new<S>(address: S) -> Result<Self, FetchError>
26 where
27 S: AsRef<str>,
28 {
29 let client = reqwest::Client::builder().cookie_store(true).build()?;
30
31 let token = Self::fetch_token(&client, address.as_ref()).await?;
32
33 Ok(Self {
34 client,
35 address: address.as_ref().to_owned(),
36 token,
37 })
38 }
39
40 async fn fetch_token(client: &reqwest::Client, address: &str) -> Result<String, FetchError> {
41 lazy_static! {
42 static ref RE: Regex =
43 Regex::new(r#"<input name="__RequestVerificationToken".*?value="([^"]+)""#)
44 .unwrap();
45 }
46 info!("fetching celcat token");
47 let body = client
48 .get(&format!("{}/LdapLogin", address))
49 .send()
50 .await?
51 .text()
52 .await?;
53
54 if let Some(token) = RE.captures(&body).and_then(|caps| caps.get(1)) {
55 Ok(token.as_str().to_owned())
56 } else {
57 Err(FetchError::Token)
58 }
59 }
60
61 pub async fn login(&mut self, username: &str, password: &str) -> Result<(), FetchError> {
62 #[derive(Debug, Serialize)]
63 struct Form<'a> {
64 #[serde(rename = "Name")]
65 username: &'a str,
66 #[serde(rename = "Password")]
67 password: &'a str,
68 #[serde(rename = "__RequestVerificationToken")]
69 token: &'a str,
70 }
71
72 info!("fetching celcat federation ids");
73 let form = Form {
74 username,
75 password,
76 token: &self.token,
77 };
78 debug!("{:?}", form);
79 self.client
80 .post(&format!("{}/LdapLogin/Logon", self.address))
81 .form(&form)
82 .send()
83 .await?;
84
85 Ok(())
86 }
87
88 pub async fn fetch<F>(&self, req: F::Request) -> Result<F, FetchError>
89 where
90 F: Fetchable,
91 {
92 let res = self
93 .client
94 .post(&format!("{}/Home/{}", self.address, F::METHOD_NAME))
95 .form(&req)
96 .send()
97 .await?
98 .json()
99 .await?;
100
101 Ok(res)
102 }
103}