1use argon2::password_hash::Salt;
3use argon2::Argon2;
4use argon2::PasswordHasher;
5use argon2::PasswordVerifier;
6use dotenvy;
7use rand::distributions::DistString;
8use rand::Rng;
9use sqlx::Postgres;
10use sqlx::QueryBuilder;
11use serde;
12use sqlx::FromRow;
13use serde::ser::SerializeStruct;
14
15pub const SESSION_VALID_FOR_SECONDS: i64 = 3600;
16
17#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
19pub struct Credentials {
20 pub user_name: String,
21 pub password: String,
22 pub realm: String, }
24
25#[derive(Debug, FromRow)]
27pub struct Session {
28 pub user_name: String,
29 pub session_token: String,
30 pub time_to_die: chrono::DateTime<chrono::Utc>
31}
32
33#[derive(Debug)]
35pub enum AddUserReturn {
36 Good(),
37 UserNotUnique(),
38 SaltFailed(),
39 HashError(String),
40 InsertError(String),
41}
42
43#[derive(Debug)]
44pub enum DeleteUserReturn {
45 Good(), FailedToDeleteSessions(String),
47 BadUserOrPassword(),
48 DataBaseError(String),
49}
50
51#[derive(Debug)]
52pub enum EndSessionReturn {
53 Ended(),
54 BadSession(),
55 DataBaseError(String),
56}
57
58#[derive(Debug)]
60pub enum UserValidatedReturn {
61 Validated(),
62 NotValidated(),
63}
64
65#[derive(Debug)]
67pub enum SessionGeneratedErr {
68 UserNotValid(),
69 FailedToAddToDatabase(String),
70}
71
72#[derive(Debug)]
74pub enum SessionValidated {
75 ValidSession(),
76 InvalidSession(),
77}
78
79pub enum SessionInvalided {
81 SucessfullyInvalidated(),
82 DidNotExist(),
83 Error(String),
84}
85
86#[derive(FromRow)]
87struct UserRow {
88 user_name: String,
89 phc: String,
90 realms: String,
91}
92
93impl serde::Serialize for Session{
94 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95 where
96 S: serde::Serializer,
97 {
98 let mut s = serializer.serialize_struct("Session", 3)?;
99 s.serialize_field("user_name", &self.user_name)?;
100 s.serialize_field("session_token", &self.session_token)?;
101 s.serialize_field("time_to_die", &self.time_to_die.to_rfc3339())?;
102 s.end()
103 }
104}
105
106fn gen_rand_string_between(min_len: u16, max_len: u16) -> String {
108 let mut rng_source = rand::thread_rng();
109 let str_len: usize = rng_source.gen_range((min_len + 1)..max_len).into();
110
111 rand::distributions::Alphanumeric.sample_string(&mut rng_source, str_len)
112}
113
114fn gen_rand_string_of_len(len: u16) -> String {
116 let mut rng_source = rand::thread_rng();
117 rand::distributions::Alphanumeric.sample_string(&mut rng_source, len.into())
118}
119
120pub async fn connect_to_db_get_pool() -> Result<sqlx::Pool<Postgres>, sqlx::Error> {
122 let dotenv_database_url_result = dotenvy::var("DATABASE_URL");
123 let data_baseurl = match dotenv_database_url_result {
124 Ok(data_baseurl) => data_baseurl,
125 Err(err) => err.to_string(),
126 };
127
128 let pool = match sqlx::postgres::PgPoolOptions::new()
130 .max_connections(5)
131 .connect(&data_baseurl)
132 .await
133 {
134 Ok(pool) => pool,
135 Err(err) => return Err(err),
136 };
137
138 return Ok(pool);
139}
140
141fn build_argon2_hasher<'a>() -> Argon2<'a> {
146 argon2::Argon2::new(
147 argon2::Algorithm::Argon2id,
148 argon2::Version::V0x13,
149 argon2::Params::new(1500, 2, 1, None).expect("Argon2 failed to generate harsher"),
150 )
151}
152
153pub async fn generate_session(
158 credentials: &Credentials,
159 pool: &sqlx::Pool<Postgres>,
160 secs_after_creation_to_die: i64,
161) -> Result<Session, SessionGeneratedErr> {
162 let _valid_user = match validate_user(&credentials, pool).await {
164 UserValidatedReturn::Validated() => (),
165 UserValidatedReturn::NotValidated() => return Err(SessionGeneratedErr::UserNotValid()),
166 };
167
168 let session = Session{
169 user_name: credentials.user_name.to_string(),
170 session_token: gen_rand_string_of_len(100),
171 time_to_die: chrono::Utc::now() + chrono::TimeDelta::seconds(secs_after_creation_to_die)
172 };
173
174 let mut sql_insert_session_builder = sqlx::QueryBuilder::new("INSERT INTO sessions (user_name, session_token, time_to_die) VALUES (");
175 sql_insert_session_builder.push_bind(&session.user_name);
176 sql_insert_session_builder.push(",");
177 sql_insert_session_builder.push_bind(&session.session_token);
178 sql_insert_session_builder.push(",");
179 sql_insert_session_builder.push_bind(session.time_to_die.to_rfc3339());
180 sql_insert_session_builder.push("::timestamp);");
181
182 let sql_insert_session = sql_insert_session_builder.build().execute(pool).await;
183
184 match sql_insert_session {
185 Ok(query_result) => match query_result.rows_affected() {
186 1 => Ok(session),
187 _ => {
188 println!("Modified: {}", query_result.rows_affected());
189 Err(SessionGeneratedErr::FailedToAddToDatabase("Got more than 0 changes".to_string()))
190 }
191 },
192 Err(err) => Err(SessionGeneratedErr::FailedToAddToDatabase(format!("{}", err.to_string())))
193 }
194}
195
196pub async fn validate_session(
200 session: &Session,
201 pool: &sqlx::Pool<Postgres>,
202) -> SessionValidated {
203 let sql_session = "SELECT user_name, session_token FROM sessions WHERE user_name=$1 AND session_token=$2 AND time_to_die > now() at time zone ('utc');";
204
205 let (db_user_name, db_session_token):(String, String) = match sqlx::query_as(&sql_session)
206 .bind(&session.user_name)
207 .bind(&session.session_token)
208 .fetch_optional(pool).await {
209 Ok(option) => match option {
210 Some(res) => res,
211 None => return SessionValidated::InvalidSession()
212 },
213 Err(_err) => return SessionValidated::InvalidSession()
214 };
215
216 if db_user_name == session.user_name && db_session_token == session.session_token {
219 return SessionValidated::ValidSession();
220 } else {
221 return SessionValidated::InvalidSession();
222 }
223}
224
225pub async fn invalidate_session(
229 session: &Session,
230 pool: &sqlx::Pool<Postgres>,
231) -> SessionInvalided {
232 let sql_session = "DELETE FROM sessions WHERE user_name=$1 AND session_token=$2;";
233
234 match sqlx::query(&sql_session)
235 .bind(&session.user_name)
236 .bind(&session.session_token)
237 .execute(pool).await {
238 Ok(res) => match res.rows_affected() {
239 1 => SessionInvalided::SucessfullyInvalidated(),
240 _ => SessionInvalided::DidNotExist(),
241 },
242 Err(err) => return SessionInvalided::Error(err.to_string())
243 }
244}
245
246
247pub async fn add_user(credentials: &Credentials, pool: &sqlx::Pool<Postgres>) -> AddUserReturn {
252 let rand_str = gen_rand_string_of_len(Salt::RECOMMENDED_LENGTH.try_into().unwrap());
255 let hasher = build_argon2_hasher();
256
257 let hash = hasher.hash_password(credentials.password.as_bytes(), &rand_str);
259 let phc_string = match hash {
260 Ok(phc_string) => phc_string.to_string(),
261 Err(err) => {
262 return AddUserReturn::HashError(format!(
263 "with pass {}, salt {}, got {}",
264 credentials.password,
265 4,
266 err.to_string()
267 ))
268 }
269 };
270 println!("with pass {}, salt {}", credentials.password, rand_str);
271
272 let mut sql_insert_user_builder: QueryBuilder<Postgres> =
274 sqlx::QueryBuilder::new("INSERT INTO users(user_name, phc, realms) VALUES (");
275 sql_insert_user_builder
276 .push_bind(&credentials.user_name)
277 .push(",")
278 .push_bind(phc_string)
279 .push(",")
280 .push_bind(&credentials.realm)
281 .push(") LIMIT 1;");
282
283 let sql_insert_build: sqlx::query::QueryAs<Postgres, UserRow, sqlx::postgres::PgArguments> =
284 sql_insert_user_builder.build_query_as::<UserRow>();
285 match sql_insert_build.fetch_optional(pool).await {
286 Ok(_) => return AddUserReturn::Good(),
287 Err(err) => {
288 let _db_err = match err.as_database_error() {
289 Some(db_err) =>
290 match db_err.to_string().find("users_pkey") {
291 Some(_match) => return AddUserReturn::UserNotUnique(),
292 None => return AddUserReturn::InsertError(err.to_string())
293 }
294 None => return AddUserReturn::InsertError(err.to_string())
295 };
296 },
297 }
298}
299
300pub async fn delete_user(credentials: &Credentials, pool: &sqlx::Pool<Postgres>) -> DeleteUserReturn {
304 let _valid_user = match validate_user(&credentials, pool).await {
306 UserValidatedReturn::Validated() => (),
307 UserValidatedReturn::NotValidated() => return DeleteUserReturn::BadUserOrPassword()
308 };
309 let mut sql_delete_session_builder = sqlx::QueryBuilder::new("DELETE FROM sessions WHERE user_name=");
311
312 sql_delete_session_builder.push_bind(&credentials.user_name).push(";");
313
314 let _sessions_deleted_option = match sql_delete_session_builder.build().fetch_optional(pool).await {
315 Ok(_) => (),
316 Err(err) => return DeleteUserReturn::FailedToDeleteSessions(err.to_string())
317 };
318
319 let mut sql_delete_use_builder = sqlx::QueryBuilder::new("DELETE FROM users WHERE user_name =");
321
322 sql_delete_use_builder.push_bind(&credentials.user_name).push(";");
323
324 let _user_deleted_option = match sql_delete_use_builder.build().fetch_optional(pool).await {
325 Ok(_) => return DeleteUserReturn::Good(),
326 Err(err) => return DeleteUserReturn::DataBaseError(err.to_string())
327 };
328}
329
330pub async fn end_sessions(session: &Session, pool: &sqlx::Pool<Postgres>) -> EndSessionReturn {
334 let _valid_session = match validate_session(session, pool).await {
335 SessionValidated::ValidSession() => (),
336 SessionValidated::InvalidSession() => return EndSessionReturn::BadSession()
337 };
338
339 let mut sql_delete_session_builder = sqlx::QueryBuilder::new("DELETE FROM sessions WHERE user_name=");
340
341 sql_delete_session_builder.push_bind(&session.user_name).push(";");
342
343 let _sessions_deleted_option = match sql_delete_session_builder.build().fetch_optional(pool).await {
344 Ok(_) => return EndSessionReturn::Ended(),
345 Err(err) => return EndSessionReturn::DataBaseError(err.to_string())
346 };
347}
348
349pub async fn validate_user(credentials: &Credentials, pool: &sqlx::Pool<Postgres>) -> UserValidatedReturn {
353 let mut sql_user_builder: QueryBuilder<Postgres> =
356 sqlx::QueryBuilder::new("SELECT user_name, phc, realms FROM users WHERE user_name=");
357
358 sql_user_builder
359 .push_bind(credentials.user_name.clone())
360 .push(";");
361
362 let user_info_option: Option<UserRow> = match sql_user_builder
363 .build_query_as::<UserRow>()
364 .fetch_optional(pool)
365 .await
366 {
367 Ok(user_info_option) => user_info_option,
368 Err(_) => {
369 println!("Fail at fetch");
370 return UserValidatedReturn::NotValidated();
371 }
372 };
373
374 let user_info: UserRow = match user_info_option {
375 Some(user_info) => user_info,
376 None => {
377 println!("Fail at User info up");
378 return UserValidatedReturn::NotValidated();
379 }
380 };
381
382 let existing_password_hash: argon2::PasswordHash =
384 match argon2::PasswordHash::new(&user_info.phc) {
385 Ok(hasher) => hasher,
386 Err(_) => {
387 println!("Fail at hash");
388 return UserValidatedReturn::NotValidated();
389 }
390 };
391
392 let argon_hasher = build_argon2_hasher(); match argon_hasher.verify_password(credentials.password.as_bytes(), &existing_password_hash) {
394 Ok(_) => UserValidatedReturn::Validated(),
395 Err(_) => UserValidatedReturn::NotValidated(),
396 }
397}