diff --git a/.gitignore b/.gitignore index 64aadb7..474d7e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ backend/target backend/db +backend/id_rsa +backend/id_rsa.pub diff --git a/backend/src/google_oauth.rs b/backend/src/google_oauth.rs index d210663..709b95d 100644 --- a/backend/src/google_oauth.rs +++ b/backend/src/google_oauth.rs @@ -61,7 +61,6 @@ pub async fn login( State(db_pool): State, Host(hostname): Host, ) -> Result { - if user_data.is_some() { // check if already authenticated return Ok(Redirect::to("/")); @@ -69,10 +68,9 @@ pub async fn login( let return_url = params .remove("return_url") - .unwrap_or_else(|| "/dashboard".to_string()); + .unwrap_or_else(|| "/".to_string()); // TODO: check if return_url is valid - println!("Return URL: {}", return_url); - + let client = get_client(hostname)?; let (pkce_code_challenge, pkce_code_verifier) = PkceCodeChallenge::new_random_sha256(); @@ -103,10 +101,15 @@ pub async fn login( pub async fn google_auth_return( Query(mut params): Query>, State(db_pool): State, + cookie: Option>, Host(hostname): Host, ) -> Result { let state = CsrfToken::new(params.remove("state").ok_or("OAuth: without state")?); let code = AuthorizationCode::new(params.remove("code").ok_or("OAuth: without code")?); + let mut headers = axum::response::AppendHeaders([( + axum::http::header::SET_COOKIE, + "session_token=".to_owned() + "; path=/; httponly; secure; samesite=strict", + )]); let query: (String, String) = sqlx::query_as( r#"DELETE FROM oauth2_state_storage WHERE csrf_state = ? RETURNING pkce_code_verifier,return_url"#, @@ -115,20 +118,8 @@ pub async fn google_auth_return( .fetch_one(&db_pool) .await?; - // Alternative: - // let query: (String, String) = sqlx::query_as( - // r#"SELECT pkce_code_verifier,return_url FROM oauth2_state_storage WHERE csrf_state = ?"#, - // ) - // .bind(state.secret()) - // .fetch_one(&db_pool) - // .await?; - // let _ = sqlx::query("DELETE FROM oauth2_state_storage WHERE csrf_state = ?") - // .bind(state.secret()) - // .execute(&db_pool) - // .await; - let pkce_code_verifier = query.0; - let return_url = query.1; + let _return_url = query.1; let pkce_code_verifier = PkceCodeVerifier::new(pkce_code_verifier); // Exchange the code with a token. @@ -190,46 +181,61 @@ pub async fn google_auth_return( .bind(email.as_str()) .fetch_one(&db_pool) .await; - let user_id = if let Ok(query) = query { + let user_id = if let Ok(query) = query + { query.0 } else { let query: (i64,) = sqlx::query_as("INSERT INTO users (email, name, family_name, given_name) VALUES (?, ?, ?, ?) RETURNING id") - .bind(email) - .bind(name) - .bind(family_name) - .bind(given_name) + .bind(email.clone()) + .bind(name.clone()) + .bind(family_name.clone()) + .bind(given_name.clone()) .fetch_one(&db_pool) .await?; + + // Add public role + sqlx::query("INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)") + .bind(query.0) + .bind("1") + .execute(&db_pool) + .await?; + query.0 }; - // Create a session for the user - let session_token_p1 = Uuid::new_v4().to_string(); - let session_token_p2 = Uuid::new_v4().to_string(); - let session_token = [session_token_p1.as_str(), "_", session_token_p2.as_str()].concat(); - let headers = axum::response::AppendHeaders([( - axum::http::header::SET_COOKIE, - "session_token=".to_owned() - + &*session_token - + "; path=/; httponly; secure; samesite=strict", - )]); - let now = Utc::now().timestamp(); + // Update session with user id or create new session + if let Some(cookie) = cookie { + if let Some(_session_token) = cookie.get("session_token") { + } else { + println!("google_auth_return : No session token"); + // Create a session for the user + let session_token_p1 = Uuid::new_v4().to_string(); + let session_token_p2 = Uuid::new_v4().to_string(); + let session_token = + [session_token_p1.as_str(), "_", session_token_p2.as_str()].concat(); + headers = axum::response::AppendHeaders([( + axum::http::header::SET_COOKIE, + "session_token=".to_owned() + + &*session_token + + "; path=/; httponly; secure; samesite=strict", + )]); + let now = Utc::now().timestamp(); - sqlx::query( - "INSERT INTO user_sessions + sqlx::query( + "INSERT INTO user_sessions (session_token_p1, session_token_p2, user_id, created_at, expires_at) VALUES (?, ?, ?, ?, ?);", - ) - .bind(session_token_p1) - .bind(session_token_p2) - .bind(user_id) - .bind(now) - .bind(now + 60 * 60 * 24) - .execute(&db_pool) - .await?; - - println!("Returning to: {}", return_url); - Ok((headers, Redirect::to(return_url.as_str()))) + ) + .bind(session_token_p1) + .bind(session_token_p2) + .bind(user_id) // Set user to anonymous + .bind(now) + .bind(now + 60 * 60 * 24) + .execute(&db_pool) + .await?; + } + } + Ok((headers, Redirect::to("/"))) } pub async fn logout( diff --git a/backend/src/main.rs b/backend/src/main.rs index 1d70b1c..ada29a3 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -15,11 +15,11 @@ mod routes; use error_handling::AppError; use middlewares::{check_auth, inject_user_data}; use google_oauth::{login, logout, google_auth_return}; -use routes::{dashboard, index, profile, user_profile, useradmin}; +use routes::{dashboard, index, about, profile, user_profile, useradmin}; #[derive(Clone)] pub struct AppState { - pub db_pool: SqlitePool, + pub db_pool: SqlitePool } #[derive(Default, Clone, Debug, FromRow, Serialize, Deserialize)] @@ -42,7 +42,7 @@ async fn main() { .connect("sqlite://db/db.sqlite3") .await; - let app_state = AppState {db_pool: db_pool.expect("Failed to get db_pool") }; + let app_state = AppState {db_pool:db_pool.expect("Failed to get db_pool")}; static MIGRATOR: Migrator = sqlx::migrate!(); @@ -55,21 +55,18 @@ async fn main() { // build our application with some routes let app = Router::new() - //Routes that require authentication .route("/dashboard", get(dashboard)) .route("/profile", get(profile)) .route("/useradmin", get(useradmin)) .route("/users/:user_id", get(user_profile)) - .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), check_auth)) - - //Routes that don't require authentication .nest_service("/assets", ServeDir::new("templates/assets") .fallback(get_service(ServeDir::new("templates/assets")))) .route("/", get(index)) + .route("/about", get(about)) .route("/login", get(login)) .route("/logout", get(logout)) - //.route("/google_auth", get(google_auth)) .route("/google_auth_return", get(google_auth_return)) + .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), check_auth)) .route_layer(middleware::from_fn_with_state(app_state.db_pool.clone(), inject_user_data)) .with_state(app_state.db_pool) .layer(Extension(user_data)) diff --git a/backend/src/middlewares.rs b/backend/src/middlewares.rs index a0de3b6..68b67c9 100644 --- a/backend/src/middlewares.rs +++ b/backend/src/middlewares.rs @@ -19,11 +19,11 @@ pub async fn inject_user_data( mut request: Request, next: Next, ) -> Result { - println!("Injecting user data"); + println!("inject_user_data : Injecting user data"); if let Some(cookie) = cookie { - println!("{:#?}", cookie.get("session_token")); + println!("inject_user_data : {:#?}", cookie.get("session_token")); if let Some(session_token) = cookie.get("session_token") { - println!("Found session token: {}", session_token); + println!("inject_user_data : Found session token: {}", session_token); let session_token: Vec<&str> = session_token.split('_').collect(); let query: Result<(i64, i64, String), _> = sqlx::query_as( r#"SELECT user_id,expires_at,session_token_p2 FROM user_sessions WHERE session_token_p1=?"#, @@ -46,7 +46,7 @@ pub async fn inject_user_data( session_token_p2_db, ) { let id = query.0; - println!("Found user: {}", id); + println!("inject_user_data : Found user: {}", id); let expires_at = query.1; if expires_at > Utc::now().timestamp() { let row = sqlx::query_as!( @@ -65,7 +65,7 @@ pub async fn inject_user_data( given_name: row.given_name, })); } else { - println!("Session expired"); + println!("inject_user_data : Session expired"); } } } @@ -82,8 +82,8 @@ pub async fn check_auth( request: Request, next: Next, ) -> Result { - println!("check_auth"); - println!("{:#?}", request); + println!("check_auth : Starting"); + //println!("{:#?}", request); if request .extensions() .get::>() @@ -91,9 +91,8 @@ pub async fn check_auth( .is_some() { let path = &*request.uri().to_string(); - println!("{}", path); println!( - "{}", + "check_auth : {}", &*request .extensions() .get::>() @@ -123,13 +122,14 @@ pub async fn check_auth( // loop through path to find a permission let mut remaining_path = Path::new(path); loop { - let query: Result<(String,), _> = sqlx::query_as(r#"select r.item from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#) + let query: Result<(String,), _> = sqlx::query_as(r#"select r.item from role_permissions r join user_roles ur on ur.role_id = r.role_id left join users u on u.id = ur.user_id where item = ? and (email = ? or r.role_id = 1)"#) .bind(remaining_path.to_str().unwrap()) .bind(request.extensions().get::>().unwrap().as_ref().unwrap().email.as_str()) .fetch_one(&app_state) .await; if let Ok(query) = query { if query.0 != "" { + println!("check_auth : found auth for {}", query.0); break; } } @@ -137,10 +137,8 @@ pub async fn check_auth( break; } remaining_path = remaining_path.parent().unwrap(); - - println!("{}", remaining_path.to_str().unwrap()); } - sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and email =?"#) + sqlx::query_as(r#"select u.id from role_permissions r join user_roles ur on ur.role_id = r.role_id join users u on u.id = ur.user_id where item = ? and (email = ? or r.role_id = 1)"#) .bind(remaining_path.to_str().unwrap()) .bind(request.extensions().get::>().unwrap().as_ref().unwrap().email.as_str()) .fetch_one(&app_state) @@ -158,7 +156,7 @@ pub async fn check_auth( 0 }; - println!("{}", user_id); + println!("check_auth : Authenticated user {}", user_id); if user_id == 0 { // user does not have the proper role // display banner and return to home page @@ -169,17 +167,11 @@ pub async fn check_auth( } else { // user is not logged in // redirect to login page with return_url - let login_url = "/login?return_url=".to_owned() + &*request.uri().to_string(); - Ok(Redirect::to(login_url.as_str()).into_response()) + //let login_url = "/login?return_url=".to_owned() + &*request.uri().to_string(); + //let login_url = "/login".to_owned(); + //Ok(Redirect::to(login_url.as_str()).into_response()) + //println!("check_auth : {:#?}", request.headers().get("referer").unwrap()); + println!("check_auth : No user data in the request"); + Ok(next.run(request).await) } } - -fn access_auth (_uri: &str, _userid: i64) { - // Check uri and userid for access and build the menu for links to the allowed resources - - let _user_auth = true; - - let _menu: Vec = vec![]; - - -} \ No newline at end of file diff --git a/backend/src/routes.rs b/backend/src/routes.rs index 9d68200..da78ed4 100644 --- a/backend/src/routes.rs +++ b/backend/src/routes.rs @@ -1,6 +1,6 @@ use askama_axum::{Response, Template}; use axum::{extract::{Path, State}, response::{Html, IntoResponse}, Extension}; -use http::{Request, StatusCode}; +use http::StatusCode; use sqlx::SqlitePool; use crate::UserData; @@ -45,23 +45,21 @@ struct DashboardTemplate { name: String, } -pub async fn index( +pub async fn index( Extension(user_data): Extension>, - _request: Request, ) -> impl IntoResponse { let user_name = user_data.map(|s| s.name); let logged_in = user_name.is_some(); let name = user_name.unwrap_or_default(); - println!("logged_in: {}, name: {}", logged_in, name); + println!("index : logged_in = {}, name = {}", logged_in, name); let template = IndexTemplate { logged_in, name }; HtmlTemplate(template) } -pub async fn dashboard( +pub async fn dashboard( Extension(user_data): Extension>, - _request: Request, ) -> impl IntoResponse { let user_name = user_data.map(|s| s.name); let logged_in = user_name.is_some(); @@ -72,9 +70,8 @@ pub async fn dashboard( } /// Handles the profile page. -pub async fn profile( +pub async fn profile( Extension(user_data): Extension>, - _request: Request, ) -> impl IntoResponse { // Extract the user's name from the user data. let user_name = user_data.as_ref().map(|s| s.name.clone()); @@ -89,11 +86,10 @@ pub async fn profile( return HtmlTemplate(template) } - pub async fn user_profile( + pub async fn user_profile( Path(user_id): Path, State(db_pool): State, Extension(user_data): Extension>, - _request: Request, ) -> impl IntoResponse { // Extract the user's name from the user data. let user_name = user_data.as_ref().map(|s| s.name.clone()); @@ -123,10 +119,9 @@ struct UserAdminTemplate { users: Vec } -pub async fn useradmin( +pub async fn useradmin( Extension(user_data): Extension>, State(db_pool): State, - _request: Request, ) -> impl IntoResponse { let user_email = user_data.map(|s| s.email); @@ -141,3 +136,22 @@ pub async fn useradmin( let template = UserAdminTemplate { logged_in, name, users }; HtmlTemplate(template) } + +#[derive(Template)] +#[template(path = "about.html")] +struct AboutTemplate { + logged_in: bool, + name: String, +} + +pub async fn about( + Extension(user_data): Extension>, +) -> impl IntoResponse { + let user_name = user_data.map(|s| s.name); + let logged_in = user_name.is_some(); + let name = user_name.unwrap_or_default(); + + let template = AboutTemplate { logged_in, name }; + HtmlTemplate(template) +} + diff --git a/backend/templates/about.html b/backend/templates/about.html index 969b23e..3c3b140 100644 --- a/backend/templates/about.html +++ b/backend/templates/about.html @@ -1,5 +1,5 @@ {% extends "base.html" %} {% block title %}About{% endblock %} {% block content %} -This is a demo OAuth website. +This is will give more information about the website {% endblock %} diff --git a/backend/toprod.sh b/backend/toprod.sh new file mode 100755 index 0000000..87cb37b --- /dev/null +++ b/backend/toprod.sh @@ -0,0 +1,5 @@ +#ssh www@192.168.59.11 'pkill jean-marie' +scp -i id_rsa target/release/jean-marie www@192.168.59.11:/opt/jean-marie +scp -i id_rsa .env www@192.168.59.11:/opt/jean-marie +scp -i id_rsa -r templates www@192.168.59.11:/opt/jean-marie +#ssh www@192.168.59.11 'cd /opt/jean-marie && ./jean-marie&'