sis_login/lib.rs
1//! A library to login to the sis system and get the moodle session
2//!
3//! [](https://crates.io/crates/sis-login)
4//! [](https://docs.rs/sis-login)
5//! [](https://crates.io/crates/sis-login)
6//! [](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¶m1=moodleLogin¶m2=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}