add tests; add authentication cookie
This commit is contained in:
		
							
								
								
									
										17
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/lib.rs
									
									
									
									
									
								
							@@ -1,2 +1,19 @@
 | 
				
			|||||||
pub mod model;
 | 
					pub mod model;
 | 
				
			||||||
pub mod rest;
 | 
					pub mod rest;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)]
 | 
				
			||||||
 | 
					#[macro_export]
 | 
				
			||||||
 | 
					macro_rules! testdb {
 | 
				
			||||||
 | 
					    () => {{
 | 
				
			||||||
 | 
					        let pool = SqlitePool::connect(":memory:").await.unwrap();
 | 
				
			||||||
 | 
					        sqlx::query_file!("./migration.sql")
 | 
				
			||||||
 | 
					            .execute(&pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        sqlx::query_file!("./seeds.sql")
 | 
				
			||||||
 | 
					            .execute(&pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        pool
 | 
				
			||||||
 | 
					    }};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,14 @@
 | 
				
			|||||||
use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
 | 
					use argon2::{password_hash::SaltString, Argon2, PasswordHasher};
 | 
				
			||||||
use serde::Serialize;
 | 
					use rocket::{
 | 
				
			||||||
 | 
					    async_trait,
 | 
				
			||||||
 | 
					    http::Status,
 | 
				
			||||||
 | 
					    request::{self, FromRequest, Outcome},
 | 
				
			||||||
 | 
					    Request,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use sqlx::{FromRow, SqlitePool};
 | 
					use sqlx::{FromRow, SqlitePool};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(FromRow, Debug, Serialize)]
 | 
					#[derive(FromRow, Debug, Serialize, Deserialize)]
 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    id: i64,
 | 
					    id: i64,
 | 
				
			||||||
    name: String,
 | 
					    name: String,
 | 
				
			||||||
@@ -16,6 +22,7 @@ pub struct User {
 | 
				
			|||||||
pub enum LoginError {
 | 
					pub enum LoginError {
 | 
				
			||||||
    SqlxError(sqlx::Error),
 | 
					    SqlxError(sqlx::Error),
 | 
				
			||||||
    InvalidAuthenticationCombo,
 | 
					    InvalidAuthenticationCombo,
 | 
				
			||||||
 | 
					    NotLoggedIn,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<sqlx::Error> for LoginError {
 | 
					impl From<sqlx::Error> for LoginError {
 | 
				
			||||||
@@ -58,28 +65,31 @@ WHERE name like ?
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl<'r> FromRequest<'r> for User {
 | 
				
			||||||
 | 
					    type Error = LoginError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
 | 
				
			||||||
 | 
					        match req.cookies().get_private("loggedin_user") {
 | 
				
			||||||
 | 
					            Some(user) => {
 | 
				
			||||||
 | 
					                let user: User = serde_json::from_str(&user.value()).unwrap(); //TODO: fixme
 | 
				
			||||||
 | 
					                Outcome::Success(user)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            None => Outcome::Failure((Status::Unauthorized, LoginError::NotLoggedIn)),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
mod test {
 | 
					mod test {
 | 
				
			||||||
 | 
					    use crate::testdb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    use super::User;
 | 
					    use super::User;
 | 
				
			||||||
    use sqlx::SqlitePool;
 | 
					    use sqlx::SqlitePool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn setup() -> SqlitePool {
 | 
					 | 
				
			||||||
        let pool = SqlitePool::connect(":memory:").await.unwrap();
 | 
					 | 
				
			||||||
        sqlx::query_file!("./migration.sql")
 | 
					 | 
				
			||||||
            .execute(&pool)
 | 
					 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
        sqlx::query_file!("./seeds.sql")
 | 
					 | 
				
			||||||
            .execute(&pool)
 | 
					 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        pool
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    #[sqlx::test]
 | 
					    #[sqlx::test]
 | 
				
			||||||
    fn succ_login_with_test_db() {
 | 
					    fn succ_login_with_test_db() {
 | 
				
			||||||
        let pool = setup().await;
 | 
					        let pool = testdb!();
 | 
				
			||||||
        User::login(&pool, "admin".into(), "admin".into())
 | 
					        User::login(&pool, "admin".into(), "admin".into())
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .unwrap();
 | 
					            .unwrap();
 | 
				
			||||||
@@ -87,7 +97,7 @@ mod test {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #[sqlx::test]
 | 
					    #[sqlx::test]
 | 
				
			||||||
    fn wrong_pw() {
 | 
					    fn wrong_pw() {
 | 
				
			||||||
        let pool = setup().await;
 | 
					        let pool = testdb!();
 | 
				
			||||||
        assert!(User::login(&pool, "admin".into(), "admi".into())
 | 
					        assert!(User::login(&pool, "admin".into(), "admi".into())
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .is_err());
 | 
					            .is_err());
 | 
				
			||||||
@@ -95,7 +105,7 @@ mod test {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    #[sqlx::test]
 | 
					    #[sqlx::test]
 | 
				
			||||||
    fn wrong_username() {
 | 
					    fn wrong_username() {
 | 
				
			||||||
        let pool = setup().await;
 | 
					        let pool = testdb!();
 | 
				
			||||||
        assert!(User::login(&pool, "admi".into(), "admin".into())
 | 
					        assert!(User::login(&pool, "admi".into(), "admin".into())
 | 
				
			||||||
            .await
 | 
					            .await
 | 
				
			||||||
            .is_err());
 | 
					            .is_err());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ async fn login(
 | 
				
			|||||||
) -> Flash<Redirect> {
 | 
					) -> Flash<Redirect> {
 | 
				
			||||||
    let user = User::login(db, login.name.clone(), login.password.clone()).await;
 | 
					    let user = User::login(db, login.name.clone(), login.password.clone()).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO: be able to use for find_by_name. This would get rid of the following match clause.
 | 
					    //TODO: be able to use ? for login. This would get rid of the following match clause.
 | 
				
			||||||
    let user = match user {
 | 
					    let user = match user {
 | 
				
			||||||
        Ok(user) => user,
 | 
					        Ok(user) => user,
 | 
				
			||||||
        Err(_) => {
 | 
					        Err(_) => {
 | 
				
			||||||
@@ -47,7 +47,7 @@ async fn login(
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let user_json: String = format!("{}", json!(user));
 | 
					    let user_json: String = format!("{}", json!(user));
 | 
				
			||||||
    cookies.add_private(Cookie::new("user", user_json));
 | 
					    cookies.add_private(Cookie::new("loggedin_user", user_json));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Flash::success(Redirect::to("/"), "Login erfolgreich")
 | 
					    Flash::success(Redirect::to("/"), "Login erfolgreich")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,48 +1,51 @@
 | 
				
			|||||||
use rocket::{get, routes, Build, Rocket};
 | 
					use rocket::{catch, catchers, get, response::Redirect, routes, Build, Rocket};
 | 
				
			||||||
use rocket_dyn_templates::{context, Template};
 | 
					use rocket_dyn_templates::{context, Template};
 | 
				
			||||||
use sqlx::SqlitePool;
 | 
					use sqlx::SqlitePool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::model::user::User;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
mod auth;
 | 
					mod auth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[get("/")]
 | 
					#[get("/")]
 | 
				
			||||||
fn index() -> Template {
 | 
					fn index(_user: User) -> Template {
 | 
				
			||||||
    Template::render("index", context! {})
 | 
					    Template::render("index", context! {})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[catch(401)] //unauthorized
 | 
				
			||||||
 | 
					fn unauthorized_error() -> Redirect {
 | 
				
			||||||
 | 
					    Redirect::to("/auth")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn start(db: SqlitePool) -> Rocket<Build> {
 | 
					pub fn start(db: SqlitePool) -> Rocket<Build> {
 | 
				
			||||||
    rocket::build()
 | 
					    rocket::build()
 | 
				
			||||||
        .manage(db)
 | 
					        .manage(db)
 | 
				
			||||||
        .mount("/", routes![index])
 | 
					        .mount("/", routes![index])
 | 
				
			||||||
        .mount("/auth", auth::routes())
 | 
					        .mount("/auth", auth::routes())
 | 
				
			||||||
 | 
					        .register("/", catchers![unauthorized_error])
 | 
				
			||||||
        .attach(Template::fairing())
 | 
					        .attach(Template::fairing())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//#[cfg(test)]
 | 
					#[cfg(test)]
 | 
				
			||||||
//mod test {
 | 
					mod test {
 | 
				
			||||||
//    use super::start;
 | 
					    use crate::testdb;
 | 
				
			||||||
//    use rocket::http::Status;
 | 
					
 | 
				
			||||||
//    use rocket::local::asynchronous::Client;
 | 
					    use super::start;
 | 
				
			||||||
//    use rocket::uri;
 | 
					    use rocket::http::Status;
 | 
				
			||||||
//    use sqlx::SqlitePool;
 | 
					    use rocket::local::asynchronous::Client;
 | 
				
			||||||
//
 | 
					    use rocket::uri;
 | 
				
			||||||
//    #[sqlx::test]
 | 
					    use sqlx::SqlitePool;
 | 
				
			||||||
//    fn hello_world() {
 | 
					
 | 
				
			||||||
//        let pool = SqlitePool::connect(":memory:").await.unwrap();
 | 
					    #[sqlx::test]
 | 
				
			||||||
//        sqlx::query_file!("./migration.sql")
 | 
					    fn test_not_logged_in() {
 | 
				
			||||||
//            .execute(&pool)
 | 
					        let pool = testdb!();
 | 
				
			||||||
//            .await
 | 
					
 | 
				
			||||||
//            .unwrap();
 | 
					        let client = Client::tracked(start(pool))
 | 
				
			||||||
//        sqlx::query_file!("./seeds.sql")
 | 
					            .await
 | 
				
			||||||
//            .execute(&pool)
 | 
					            .expect("valid rocket instance");
 | 
				
			||||||
//            .await
 | 
					        let response = client.get(uri!(super::index)).dispatch().await;
 | 
				
			||||||
//            .unwrap();
 | 
					
 | 
				
			||||||
//
 | 
					        assert_eq!(response.status(), Status::SeeOther);
 | 
				
			||||||
//        let client = Client::tracked(start())
 | 
					        let location = response.headers().get("Location").next().unwrap();
 | 
				
			||||||
//            .await
 | 
					        assert_eq!(location, "/auth");
 | 
				
			||||||
//            .expect("valid rocket instance");
 | 
					    }
 | 
				
			||||||
//        let response = client.get(uri!(super::index)).dispatch().await;
 | 
					}
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        assert_eq!(response.status(), Status::Ok);
 | 
					 | 
				
			||||||
//        assert_eq!(response.into_string().await, Some("Hello, world!".into()));
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
//}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user