sis_login/
lib.rs

1//! A library to login to the sis system and get the moodle session
2//!
3//! [![crates.io](https://img.shields.io/crates/v/sis-login.svg)](https://crates.io/crates/sis-login)
4//! [![docs.rs](https://docs.rs/sis-login/badge.svg)](https://docs.rs/sis-login)
5//! [![downloads](https://img.shields.io/crates/d/sis-login.svg)](https://crates.io/crates/sis-login)
6//! [![license](https://img.shields.io/crates/l/sis-login.svg)](https://github.com/0x61nas/sis-login/blob/aurora/LICENSE)
7//!
8//! # Example
9//! ```no_run
10//! use sis_login::Sis;
11//! use sis_login::sis::types::user_type::UserType;
12//!
13//! #[tokio::main]
14//! async fn main() {
15//!    let username = std::env::var("SIS_USERNAME").unwrap();
16//!    let password = std::env::var("SIS_PASSWORD").unwrap();
17//!
18//!    // Crate Sis instance
19//!    let headers_builder = sis_login::headers_builder::DefaultHeadersBuilder::new(
20//!       "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0".to_string(),
21//!      "https://sis.eelu.edu.eg/static/PortalStudent.html".to_string()
22//!   );
23//!
24//!    let login_url: &str = "https://sis.eelu.edu.eg/studentLogin";
25//!    let get_moodle_session_url: &str = "https://sis.eelu.edu.eg/getJCI";
26//!    let mut sis = Sis::new(login_url, get_moodle_session_url, &headers_builder);
27//!
28//!   // Login to sis
29//!    match sis.login(&username, &password, UserType::Student).await {
30//!         Ok(_) => {
31//!             println!("Login Success");
32//!            // Get moodle session link
33//!           let Ok(moodle_session_link) = sis.get_moodle_session_link().await else { panic!("Failed to get moodle session link") };
34//!           println!("Moodle session link: {}", moodle_session_link);
35//!        },
36//!         Err(err) => println!("Login Failed: {}", err),
37//!     }
38//! }
39//!```
40//! # Features
41//! * `debug` - Enable debug logs, you still need to use a logger like env_logger and initialize it in your code
42//!
43//! # Contributing
44//! I'm happy to accept any contributions, just consider reading the [CONTRIBUTING.md](https://github.com/0x61nas/sis-login/blob/aurora/CONTRIBUTING.md) guide first. to avoid waste waste our time on some unnecessary things.
45//!
46//! > the main keywords are: **signed commits**, **conventional commits**, **no emojis**, **linear history**, **the PR shouldn't have more than tree commits most of the time**
47//!
48//! # License
49//! This project is licensed under [MIT license][mit].
50//!
51//! [mit]: https://github.com/0x61nas/sis-login/blob/aurora/LICENSE
52//!
53//!
54
55pub mod headers_builder;
56pub mod sis;
57
58use crate::sis::types::sis_response::{LoginResult, MoodleLoginResult};
59use crate::sis::types::user_type::UserType;
60use crate::sis::utils;
61#[cfg(feature = "debug")]
62use log::{debug, error, info};
63use std::future::{IntoFuture, Ready};
64
65/// The error type for the Sis struct
66#[derive(Debug, thiserror::Error)]
67pub enum SisError {
68    /// There was an error while sending the request to the server (It can be a network error or a server error)
69    #[error("Requset error: `{0}`")]
70    SendRequestError(reqwest::Error),
71    /// There was an error creating the client that will be used to send the requests
72    #[error("Can't create the cliet: `{0}`")]
73    CreateClientError(reqwest::Error),
74    /// The provided username or password is incorrect
75    #[error("Authentication error")]
76    AuthError,
77    /// There was an error while parsing the response from the server (Unexpected response)
78    #[error("Unexpected response")]
79    ParseLoginResultError,
80}
81
82impl IntoFuture for SisError {
83    type Output = SisError;
84    type IntoFuture = Ready<Self::Output>;
85
86    fn into_future(self) -> Self::IntoFuture {
87        std::future::ready(self)
88    }
89}
90
91/// A Result type alias for SisError
92pub type Result<T> = std::result::Result<T, SisError>;
93
94/// This struct is used to login to the sis system and get the moodle session.
95pub struct Sis<'a> {
96    login_url: String,
97    get_moodle_session_url: String,
98    cookies: String,
99    headers_builder: &'a (dyn headers_builder::HeadersBuilder + 'a),
100}
101
102impl<'a> Sis<'a> {
103    /// Create a new Sis instance
104    ///
105    /// # Arguments
106    /// * `login_url` - The login url of the sis system (It varies by university, for example in EELU it's https://sis.eelu.edu.eg/studentLogin)
107    /// * `get_moodle_session_url` - The url to get the moodle session (It varies by university, for example in EELU it's https://sis.eelu.edu.eg/getJCI)
108    /// * `headers_builder` - The headers builder to use (In most cases you can use the default one or you can create your own if you want more control)
109    ///
110    /// # Example
111    /// ```no_run
112    /// # use sis_login::Sis;
113    /// # use sis_login::headers_builder::DefaultHeadersBuilder;
114    /// # use sis_login::sis::types::user_type::UserType;
115    ///
116    /// #[tokio::main]
117    /// async fn main() {
118    ///    let headers_builder = DefaultHeadersBuilder::new(
119    ///       "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0".to_string(),
120    ///      "https://sis.eelu.edu.eg/static/PortalStudent.html".to_string(),
121    ///   );
122    ///  let mut sis = Sis::new(
123    ///     "https://sis.eelu.edu.eg/studentLogin",
124    ///    "https://sis.eelu.edu.eg/getJCI",
125    ///     &headers_builder,
126    ///  );
127    ///
128    ///  // Use the sis instance here...
129    /// }
130    pub fn new(
131        login_url: &str,
132        get_moodle_session_url: &str,
133        headers_builder: &'a (dyn headers_builder::HeadersBuilder + 'a),
134    ) -> Self {
135        Sis {
136            login_url: login_url.to_string(),
137            get_moodle_session_url: get_moodle_session_url.to_string(),
138            cookies: String::new(),
139            headers_builder,
140        }
141    }
142
143    /// Login to sis
144    /// # Arguments
145    /// * `username` - The username of the user
146    /// * `password` - The password of the user
147    /// * `usertype` - The type of the user (Student or Staff or System user)
148    ///
149    /// # Example
150    /// ```no_run
151    /// # use sis_login::Sis;
152    /// # use sis_login::sis::types::user_type::UserType;
153    ///
154    /// #[tokio::main]
155    /// async fn main() {
156    ///    let username = std::env::var("SIS_USERNAME").unwrap();
157    ///    let password = std::env::var("SIS_PASSWORD").unwrap();
158    ///
159    ///    // Crate Sis instance
160    ///    let headers_builder = sis_login::headers_builder::DefaultHeadersBuilder::new(
161    ///       "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0".to_string(),
162    ///      "https://sis.eelu.edu.eg/static/PortalStudent.html".to_string()
163    ///   );
164    ///
165    ///    let login_url: &str = "https://sis.eelu.edu.eg/studentLogin";
166    ///    let get_moodle_session_url: &str = "https://sis.eelu.edu.eg/getJCI";
167    ///    let mut sis = Sis::new(login_url, get_moodle_session_url, &headers_builder);
168    ///
169    ///   // Login to sis
170    ///    match sis.login(&username, &password, UserType::Student).await {
171    ///         Ok(_) => println!("Login Success"),
172    ///         Err(err) => println!("Login Failed: {}", err),
173    ///     }
174    /// }
175    ///```
176    ///
177    /// # Errors
178    /// * `SisError::SendRequestError` - If there is an error while sending the request (e.g. network error)
179    /// * `SisError::CreateClientError` - If there is an error while creating the client (e.g. invalid url)
180    /// * `SisError::AuthError` - If the provided username or password is incorrect
181    /// * `SisError::ParseLoginResultError` - If there is an error while parsing the login result
182    pub async fn login(
183        &mut self,
184        username: &String,
185        password: &String,
186        usertype: UserType,
187    ) -> Result<()> {
188        // let login_url: &str = "https://sis.eelu.edu.eg/studentLogin";
189        let data = format!(
190            "UserName={}&Password={}&userType={}",
191            username,
192            password,
193            usertype.to_num()
194        );
195
196        #[cfg(feature = "debug")]
197        debug!(
198            "Trying Login With => Username : {} , Password : {}  , As {}",
199            username,
200            password,
201            usertype.to_string()
202        );
203
204        let response =
205            utils::send_request(self.login_url.as_str(), data, self.headers_builder.build())
206                .await?;
207
208        let res_headers = &response.headers().clone();
209
210        #[cfg(feature = "debug")]
211        debug!("Response Headers: {:?}", res_headers);
212
213        let login_result = match response.json::<LoginResult>().await {
214            Ok(result) => result,
215            Err(_err) => {
216                #[cfg(feature = "debug")]
217                debug!("[-] Error While Parse Login Result : {}", err);
218                return Err(SisError::ParseLoginResultError);
219            }
220        };
221
222        #[cfg(feature = "debug")]
223        debug!("Login Result: {:?}", login_result);
224
225        if login_result.rows[0].row.login_ok.as_str() == "True" {
226            #[cfg(feature = "debug")]
227            {
228                info!("[+] Login Success");
229                info!("[+] Getteing Session Moodle URL ...");
230            }
231            self.cookies = utils::parse_cookies(res_headers);
232            Ok(())
233        } else {
234            Err(SisError::AuthError)
235        }
236    }
237
238    /// Get Moodle Session URL
239    ///
240    /// # Example
241    /// ```no_run
242    /// # use sis_login::Sis;
243    /// # use sis_login::sis::types::user_type::UserType;
244    ///
245    /// #[tokio::main]
246    /// async fn main() {
247    ///     let username = std::env::var("SIS_USERNAME").unwrap();
248    ///     let password = std::env::var("SIS_PASSWORD").unwrap();
249    ///
250    ///     // Crate Sis instance
251    ///     let headers_builder = sis_login::headers_builder::DefaultHeadersBuilder::new(
252    ///         "Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0".to_string(),
253    ///         "https://sis.eelu.edu.eg/static/PortalStudent.html".to_string()
254    ///     );
255    ///
256    ///     let login_url: &str = "https://sis.eelu.edu.eg/studentLogin";
257    ///     let get_moodle_session_url: &str = "https://sis.eelu.edu.eg/getJCI";
258    ///     let mut sis = Sis::new(login_url, get_moodle_session_url, &headers_builder);
259    ///
260    ///     // Login to sis
261    ///     match sis.login(&username, &password, UserType::Student).await {
262    ///         Ok(_) => println!("Login Success"),
263    ///         Err(err) => println!("Login Failed: {}", err),
264    ///     }
265    ///
266    ///     // Get Moodle Session URL
267    ///     match sis.get_moodle_session().await {
268    ///         Ok(url) => println!("Moodle Session URL: {}", url),
269    ///         Err(err) => println!("Error While Get Moodle Session URL: {}", err),
270    ///     }
271    /// }
272    /// ```
273    ///
274    /// # Errors
275    /// * `SisError::SendRequestError` - If there is an error while sending the request (e.g. network error)
276    /// * `SisError::CreateClientError` - If there is an error while creating the client (e.g. invalid url)
277    /// * `SisError::ParseLoginResultError` - If there is an error while parsing the login result (e.g. invalid response)
278    pub async fn get_moodle_session(&self) -> Result<String> {
279        // let url: &str = "https://sis.eelu.edu.eg/getJCI";
280
281        let response = utils::send_request(
282            self.get_moodle_session_url.as_str(),
283            "param0=stuAdmission.stuAdmission&param1=moodleLogin&param2=2".to_string(),
284            self.headers_builder
285                .build_with_cookies(self.cookies.as_str()),
286        )
287        .await?;
288
289        match response.json::<MoodleLoginResult>().await {
290            Ok(result) => Ok(result.login_url),
291            Err(_err) => {
292                #[cfg(feature = "debug")]
293                error!("[-] Error While Parse Login Result : {}", _err);
294                Err(SisError::ParseLoginResultError)
295            }
296        }
297    }
298}