mini_rust_auth/
lib.rs

1//! ⛔ Warning ⛔: This crate is in a very early state and there are still many features I plan to add. In all honesty I don't think you should use it until this message is gone :).
2//!
3//! # Mini Rust Auth
4//!  
5//! Some people say that you should never implement you own auth and they are here I am doing it maybe wrong.
6//! This was developed for a personal project and I delivery it with no grantee of it working.
7//!
8//! ## Goal
9//!
10//! This crate builds out a easy to work with API for auth.
11//! It manages the creation, deletion, and verification of users.
12//! It also manages the creation, deletion, and validation of sessions.
13//!
14//! ## Project overview
15//!
16//! The `rust_auth::auth` module provides a api that can be used in any project.
17//! It also provides `rust_auth::wrappers` which provides wrappers around the functions in `rust_auth::auth` that can be used with actix_web as endpoints.
18//!
19//! The binary built delivers a actix_web based api.
20//!
21//! ## **Security notes**
22//!
23//! This is based on 'Argon2' as of now. All commination should be done over tls. If you want yo use this feel free but be aware that I am no security expert.
24pub mod auth;
25pub mod session;
26pub mod wrappers;
27
28#[cfg(test)]
29mod session_test {
30    use crate::auth;
31    use crate::auth::AddUserReturn;
32    use crate::session;
33    use crate::session::generate_session;
34    use actix_web::{http::header::ContentType, test, App, HttpMessage};
35
36    /// Connects to database pool
37    async fn connect_to_pool() -> sqlx::Pool<sqlx::Postgres> {
38        match auth::connect_to_db_get_pool().await {
39            Ok(pool) => pool,
40            Err(_) => panic!("could not connect to db"),
41        }
42    }
43
44    async fn complete_migrations(pool: &sqlx::Pool<sqlx::Postgres>) -> () {
45        let sqlx_migrator = sqlx::migrate!();
46        let _migration_undo = match sqlx_migrator.undo(pool, 0).await {
47            Ok(_) => true,
48            Err(err) => return assert!(false, "migrator failed with err: {}", err.to_string()),
49        };
50
51        let _migration_run = match sqlx_migrator.run(pool).await {
52            Ok(_) => true,
53            Err(_) => return assert!(false, "migrator failed run"),
54        };
55    }
56
57    use super::*;
58
59    #[actix_web::test]
60    async fn test_gen_and_valid_session_based() {
61        let postgres_pool = match auth::connect_to_db_get_pool().await {
62            Ok(pool) => pool,
63            Err(_err) => panic!("cound not connect to db"),
64        };
65
66        complete_migrations(&postgres_pool).await;
67
68        let creds = auth::Credentials {
69            user_name: "test_user_session_based".to_string().to_owned(),
70            password: "mypass".to_string().to_owned(),
71            realm: "user".to_string().to_owned(),
72        };
73
74        let _user_added = match auth::add_user(&creds, &postgres_pool).await {
75            AddUserReturn::Good() => (),
76            _ => panic!("Add user failed"),
77        };
78
79        let app = test::init_service(
80            App::new()
81                .app_data(actix_web::web::Data::new(postgres_pool.clone()))
82                .wrap(actix_session::SessionMiddleware::new(
83                    actix_session::storage::CookieSessionStore::default(),
84                    actix_web::cookie::Key::from(
85                        "wfjro2f2ofj293fj23f2dfljw;fljf2lkfskjdf;slkdfjsd;lkfjsd;lfksjflkdjj23fkj3".as_bytes(),
86                    ),
87                ))
88                .route(
89                    "/generate_session",
90                    actix_web::web::get().to(session::generate_session_web_resp),
91                )
92                .route(
93                    "/validate_session",
94                    actix_web::web::get().to(session::validate_session_web_resp),
95                ),
96        )
97        .await;
98
99        let gen_sesh_req = test::TestRequest::get()
100            .uri("/generate_session")
101            .set_json(&creds)
102            // .insert_header(ContentType::plaintext())
103            .to_request();
104        let resp = test::call_service(&app, gen_sesh_req).await;
105        
106        let cookies = match resp.response().cookies().next() {
107            Some(cookie) => cookie,
108            None => panic!("Cookie was not set")
109        };
110        let validate_session_request = test::TestRequest::get()
111            .uri("/validate_session")
112            .cookie(cookies)
113            .to_request();
114        let resp = test::call_service(&app, validate_session_request).await;
115        println!("{:?}", &resp.status());
116        println!("{:?}", &resp.response().body());
117        assert!(resp.status().is_success());
118    }
119}
120
121#[cfg(test)]
122mod auth_tests {
123    use crate::{auth::{self, EndSessionReturn}, session::EndSessionsReturn};
124
125    /// Connects to database pool
126    async fn connect_to_pool() -> sqlx::Pool<sqlx::Postgres> {
127        match auth::connect_to_db_get_pool().await {
128            Ok(pool) => pool,
129            Err(_) => panic!("could not connect to db"),
130        }
131    }
132
133    /// Will erase and reset the current db for testing
134    async fn complete_migrations(pool: &sqlx::Pool<sqlx::Postgres>) -> () {
135        let sqlx_migrator = sqlx::migrate!();
136        let _migration_undo = match sqlx_migrator.undo(pool, 0).await {
137            Ok(_) => true,
138            Err(err) => return assert!(false, "migrator failed with err: {}", err.to_string()),
139        };
140
141        let _migration_run = match sqlx_migrator.run(pool).await {
142            Ok(_) => true,
143            Err(_) => return assert!(false, "migrator failed run"),
144        };
145    }
146
147    /// Tests that we are able to generate a session
148    #[actix_web::test]
149    async fn get_session() {
150        let pool = connect_to_pool().await;
151        complete_migrations(&pool).await;
152
153        let creds = auth::Credentials {
154            user_name: "test_user".to_string().to_owned(),
155            password: "my_pass".to_string().to_owned(),
156            realm: "user".to_string().to_owned(),
157        };
158
159        let _add_user_result = match auth::add_user(&creds, &pool).await {
160            auth::AddUserReturn::Good() => (),
161            _ => (), // Pass bc other tests may have inserted user
162        };
163
164        match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
165            Ok(session) => {
166                assert!(session.user_name == creds.user_name && session.session_token != "")
167            }
168            Err(err) => panic!("the test for get session failed with: {:?}", err),
169        }
170    }
171
172    /// Checks that after a session is created it can be properly validated
173    #[actix_web::test]
174    async fn verify_session() {
175        let pool = connect_to_pool().await;
176        complete_migrations(&pool).await;
177
178        let creds = auth::Credentials {
179            user_name: "test_user_verify_session".to_string().to_owned(),
180            password: "mypass".to_string().to_owned(),
181            realm: "user".to_string().to_owned(),
182        };
183
184        let _add_user_result = match auth::add_user(&creds, &pool).await {
185            auth::AddUserReturn::Good() => (),
186            _ => panic!("Add user failed"),
187        };
188
189        let session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
190            Ok(session) => session,
191            Err(err) => {
192                return assert!(
193                    false,
194                    "Generate session got error:{:?}\non user:{:?}",
195                    err, creds
196                )
197            }
198        };
199
200        match auth::validate_session(&session, &pool).await {
201            auth::SessionValidated::ValidSession() => assert!(true, "Session validated"),
202            auth::SessionValidated::InvalidSession() => {
203                assert!(false, "Session wrongly invalidated")
204            }
205        }
206    }
207
208    #[actix_web::test]
209    async fn verify_session_invalid_token_end() {
210        let pool = connect_to_pool().await;
211        complete_migrations(&pool).await;
212
213        let creds = auth::Credentials {
214            user_name: "test_user".to_string().to_owned(),
215            password: "my_pass".to_string().to_owned(),
216            realm: "user".to_string().to_owned(),
217        };
218
219        let _add_user_result = match auth::add_user(&creds, &pool).await {
220            auth::AddUserReturn::Good() => (),
221            _ => (),
222        };
223
224        let mut session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
225            Ok(session) => session,
226            Err(err) => return assert!(false, "{:?}", err),
227        };
228
229        // Alter session token such that it no longer matches what is in the db
230        let replace_last_char_with = match session.session_token.pop() {
231            Some(c) => {
232                if c == 'a' {
233                    'b'
234                } else {
235                    'a'
236                }
237            }
238            None => 'a',
239        };
240
241        session.session_token.push(replace_last_char_with);
242        match auth::validate_session(&session, &pool).await {
243            auth::SessionValidated::ValidSession() => assert!(false, "Session validated wrongly"),
244            auth::SessionValidated::InvalidSession() => {
245                assert!(true, "Session correctly invalidated")
246            }
247        }
248    }
249
250    #[actix_web::test]
251    async fn verify_session_invalid_user_name() {
252        let pool = connect_to_pool().await;
253        complete_migrations(&pool).await;
254
255        let creds = auth::Credentials {
256            user_name: "test_user".to_string().to_owned(),
257            password: "my_pass".to_string().to_owned(),
258            realm: "user".to_string().to_owned(),
259        };
260
261        let _add_user_result = match auth::add_user(&creds, &pool).await {
262            auth::AddUserReturn::Good() => (),
263            _ => (),
264        };
265
266        let mut session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
267            Ok(session) => session,
268            Err(err) => return assert!(false, "{:?}", err),
269        };
270
271        // Alter session token such that it no longer matches what is in the db
272        session.user_name = "".to_string();
273        match auth::validate_session(&session, &pool).await {
274            auth::SessionValidated::ValidSession() => assert!(false, "Session validated wrongly"),
275            auth::SessionValidated::InvalidSession() => {
276                assert!(true, "Session correctly invalidated")
277            }
278        }
279    }
280
281    #[actix_web::test]
282    async fn invalidate_session_test() {
283        let pool = connect_to_pool().await;
284        complete_migrations(&pool).await;
285
286        let creds = auth::Credentials {
287            user_name: "test_user".to_string().to_owned(),
288            password: "my_pass".to_string().to_owned(),
289            realm: "user".to_string().to_owned(),
290        };
291
292        let _add_user_result = match auth::add_user(&creds, &pool).await {
293            auth::AddUserReturn::Good() => (),
294            _ => (),
295        };
296
297        let session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
298            Ok(session) => session,
299            Err(err) => return assert!(false, "{:?}", err),
300        };
301
302        match auth::invalidate_session(&session, &pool).await {
303            auth::SessionInvalided::SucessfullyInvalidated() => {
304                match auth::validate_session(&session, &pool).await {
305                    auth::SessionValidated::ValidSession() => {
306                        panic!("Session was reported invalidated but was still returning as valid")
307                    }
308                    auth::SessionValidated::InvalidSession() => {
309                        assert!(true, "Session invalidated correctly")
310                    }
311                }
312            }
313
314            _ => assert!(false, "Session invalidated error"),
315        }
316    }
317
318    #[actix_web::test]
319    async fn end_sessions() {
320        let pool = connect_to_pool().await;
321        complete_migrations(&pool).await;
322
323        let creds = auth::Credentials {
324            user_name: "test_user".to_string().to_owned(),
325            password: "my_pass".to_string().to_owned(),
326            realm: "user".to_string().to_owned(),
327        };
328
329        let _add_user_result = match auth::add_user(&creds, &pool).await {
330            auth::AddUserReturn::Good() => (),
331            _ => (),
332        };
333
334        let session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
335            Ok(session) => session,
336            Err(err) => return assert!(false, "{:?}", err),
337        };
338
339        let _session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
340            Ok(session) => session,
341            Err(err) => return assert!(false, "{:?}", err),
342        };
343        match auth::end_sessions(&session, &pool).await {
344            auth::EndSessionReturn::Ended() => println!("Sessions Dropped"),
345            _ => assert!(false, "Session falsely invalidated"),
346        }
347
348        let mut sql_check_delete_session_builder = sqlx::QueryBuilder::new("SELECT * FROM sessions WHERE user_name='test_user';");
349
350        println!("{:?}", sql_check_delete_session_builder.sql());
351        match sql_check_delete_session_builder.build().fetch_all(&pool).await {
352            Ok(rows) => {
353                assert!(rows.len() == 0, "Sessions dropped");
354            },
355            Err(err) => {
356                assert!(false, "got err so sessions prob dropped");
357            }
358        }
359    }
360
361    #[actix_web::test]
362    async fn delete_user() {
363        let pool = connect_to_pool().await;
364        complete_migrations(&pool).await;
365
366        let creds = auth::Credentials {
367            user_name: "test_user".to_string().to_owned(),
368            password: "my_pass".to_string().to_owned(),
369            realm: "user".to_string().to_owned(),
370        };
371
372        let _add_user_result = match auth::add_user(&creds, &pool).await {
373            auth::AddUserReturn::Good() => (),
374            _ => (),
375        };
376
377        let _session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
378            Ok(session) => session,
379            Err(err) => return assert!(false, "{:?}", err),
380        };
381
382        let _session = match auth::generate_session(&creds, &pool, auth::SESSION_VALID_FOR_SECONDS).await {
383            Ok(session) => session,
384            Err(err) => return assert!(false, "{:?}", err),
385        };
386
387        match auth::delete_user(&creds, &pool).await {
388            auth::DeleteUserReturn::BadUserOrPassword() => assert!(false, "Error bad user or pass flag"),
389            auth::DeleteUserReturn::FailedToDeleteSessions(msg) => assert!(false, "Could not delete session msg: {msg}"),
390            auth::DeleteUserReturn::DataBaseError(msg) => assert!(false, "Database error: {msg}"),
391            auth::DeleteUserReturn::Good() => assert!(true, "delete says it was good"),
392        }
393        
394        // Check sessions are gone 
395        let mut sql_check_delete_session_builder = sqlx::QueryBuilder::new("SELECT * FROM sessions WHERE user_name='test_user';");
396
397        println!("{:?}", sql_check_delete_session_builder.sql());
398        match sql_check_delete_session_builder.build().fetch_all(&pool).await {
399            Ok(rows) => {
400                assert!(rows.len() == 0, "Sessions dropped");
401            },
402            Err(_err) => {
403                assert!(false, "got err so sessions prob dropped");
404            }
405        }
406        
407        // Check user is gone
408        let mut sql_check_user_deleted_builder = sqlx::QueryBuilder::new("SELECT * FROM users WHERE user_name='test_user';");
409
410        println!("{:?}", sql_check_user_deleted_builder.sql());
411        match sql_check_user_deleted_builder.build().fetch_all(&pool).await {
412            Ok(rows) => {
413                assert!(rows.len() == 0, "users dropped");
414            },
415            Err(_err) => {
416                assert!(false, "got err so users prob dropped");
417            }
418        }
419    }
420}