Extract user information from session
This commit is contained in:
		@@ -1,3 +1,6 @@
 | 
				
			|||||||
 | 
					/// Header used to authenticate API requests made using a token
 | 
				
			||||||
 | 
					pub const API_TOKEN_HEADER: &str = "X-Auth-Token";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Session-specific constants
 | 
					/// Session-specific constants
 | 
				
			||||||
pub mod sessions {
 | 
					pub mod sessions {
 | 
				
			||||||
    /// OpenID auth session state key
 | 
					    /// OpenID auth session state key
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
use crate::app_config::AppConfig;
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
use crate::controllers::{HttpFailure, HttpResult};
 | 
					use crate::controllers::{HttpFailure, HttpResult};
 | 
				
			||||||
 | 
					use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod};
 | 
				
			||||||
use crate::extractors::money_session::MoneySession;
 | 
					use crate::extractors::money_session::MoneySession;
 | 
				
			||||||
use crate::services::users_service;
 | 
					use crate::services::users_service;
 | 
				
			||||||
use actix_remote_ip::RemoteIP;
 | 
					use actix_remote_ip::RemoteIP;
 | 
				
			||||||
@@ -104,3 +105,23 @@ pub async fn finish_oidc(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Ok(HttpResponse::Ok().finish())
 | 
					    Ok(HttpResponse::Ok().finish())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Get current user information
 | 
				
			||||||
 | 
					pub async fn auth_info(auth: AuthExtractor) -> HttpResult {
 | 
				
			||||||
 | 
					    Ok(HttpResponse::Ok().json(auth.user))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Sign out user
 | 
				
			||||||
 | 
					pub async fn sign_out(auth: AuthExtractor, session: MoneySession) -> HttpResult {
 | 
				
			||||||
 | 
					    match auth.method {
 | 
				
			||||||
 | 
					        AuthenticatedMethod::Cookie => {
 | 
				
			||||||
 | 
					            session.unset_current_user()?;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        AuthenticatedMethod::Dev => {
 | 
				
			||||||
 | 
					            // Nothing to be done, user is always authenticated
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(HttpResponse::NoContent().finish())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										61
									
								
								moneymgr_backend/src/extractors/auth_extractor.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								moneymgr_backend/src/extractors/auth_extractor.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					use crate::app_config::AppConfig;
 | 
				
			||||||
 | 
					use crate::extractors::money_session::MoneySession;
 | 
				
			||||||
 | 
					use crate::models::users::User;
 | 
				
			||||||
 | 
					use crate::services::users_service;
 | 
				
			||||||
 | 
					use actix_web::dev::Payload;
 | 
				
			||||||
 | 
					use actix_web::error::ErrorUnauthorized;
 | 
				
			||||||
 | 
					use actix_web::{Error, FromRequest, HttpRequest};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
 | 
					pub enum AuthenticatedMethod {
 | 
				
			||||||
 | 
					    /// User is authenticated using a cookie
 | 
				
			||||||
 | 
					    Cookie,
 | 
				
			||||||
 | 
					    /// User is authenticated through command line, for debugging purposes only
 | 
				
			||||||
 | 
					    Dev,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Authentication extractor. Extract authentication information from request
 | 
				
			||||||
 | 
					pub struct AuthExtractor {
 | 
				
			||||||
 | 
					    pub method: AuthenticatedMethod,
 | 
				
			||||||
 | 
					    pub user: User,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl FromRequest for AuthExtractor {
 | 
				
			||||||
 | 
					    type Error = Error;
 | 
				
			||||||
 | 
					    type Future = futures_util::future::LocalBoxFuture<'static, Result<Self, Self::Error>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn from_request(req: &HttpRequest, _payload: &mut Payload) -> Self::Future {
 | 
				
			||||||
 | 
					        let req = req.clone();
 | 
				
			||||||
 | 
					        Box::pin(async move {
 | 
				
			||||||
 | 
					            // Check if login is hard-coded as program argument
 | 
				
			||||||
 | 
					            if let Some(email) = &AppConfig::get().unsecure_auto_login_email {
 | 
				
			||||||
 | 
					                let user = users_service::get_user_by_email(email).map_err(|e| {
 | 
				
			||||||
 | 
					                    log::error!("Failed to retrieve dev user: {e}");
 | 
				
			||||||
 | 
					                    ErrorUnauthorized("Unable to retrieve dev user!")
 | 
				
			||||||
 | 
					                })?;
 | 
				
			||||||
 | 
					                return Ok(Self {
 | 
				
			||||||
 | 
					                    method: AuthenticatedMethod::Dev,
 | 
				
			||||||
 | 
					                    user,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Check for cookie authentication
 | 
				
			||||||
 | 
					            let session = MoneySession::extract(&req).await?;
 | 
				
			||||||
 | 
					            if let Some(user_id) = session.current_user().map_err(|e| {
 | 
				
			||||||
 | 
					                log::error!("Failed to retrieve user id: {e}");
 | 
				
			||||||
 | 
					                ErrorUnauthorized("Failed to read session information!")
 | 
				
			||||||
 | 
					            })? {
 | 
				
			||||||
 | 
					                let user = users_service::get_user_by_id(user_id).map_err(|e| {
 | 
				
			||||||
 | 
					                    log::error!("Failed to retrieve user from cookie session: {e}");
 | 
				
			||||||
 | 
					                    ErrorUnauthorized("Failed to retrieve user information!")
 | 
				
			||||||
 | 
					                })?;
 | 
				
			||||||
 | 
					                return Ok(Self {
 | 
				
			||||||
 | 
					                    method: AuthenticatedMethod::Cookie,
 | 
				
			||||||
 | 
					                    user,
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(ErrorUnauthorized("Authentication required!"))
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1 +1,2 @@
 | 
				
			|||||||
 | 
					pub mod auth_extractor;
 | 
				
			||||||
pub mod money_session;
 | 
					pub mod money_session;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
use crate::constants;
 | 
					use crate::constants;
 | 
				
			||||||
use crate::models::users::User;
 | 
					use crate::models::users::{User, UserID};
 | 
				
			||||||
use crate::utils::rand_utils::rand_string;
 | 
					use crate::utils::rand_utils::rand_string;
 | 
				
			||||||
use actix_session::Session;
 | 
					use actix_session::Session;
 | 
				
			||||||
use actix_web::dev::Payload;
 | 
					use actix_web::dev::Payload;
 | 
				
			||||||
@@ -63,6 +63,17 @@ impl MoneySession {
 | 
				
			|||||||
        self.0.insert(constants::sessions::USER_ID, user.id())?;
 | 
					        self.0.insert(constants::sessions::USER_ID, user.id())?;
 | 
				
			||||||
        Ok(())
 | 
					        Ok(())
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get current user
 | 
				
			||||||
 | 
					    pub fn current_user(&self) -> anyhow::Result<Option<UserID>> {
 | 
				
			||||||
 | 
					        Ok(self.0.get(constants::sessions::USER_ID)?)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Remove defined user
 | 
				
			||||||
 | 
					    pub fn unset_current_user(&self) -> anyhow::Result<()> {
 | 
				
			||||||
 | 
					        self.0.remove(constants::sessions::USER_ID);
 | 
				
			||||||
 | 
					        Ok(())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl FromRequest for MoneySession {
 | 
					impl FromRequest for MoneySession {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,8 +10,8 @@ use actix_web::{App, HttpServer, web};
 | 
				
			|||||||
use moneymgr_backend::app_config::AppConfig;
 | 
					use moneymgr_backend::app_config::AppConfig;
 | 
				
			||||||
use moneymgr_backend::connections::{db_connection, s3_connection};
 | 
					use moneymgr_backend::connections::{db_connection, s3_connection};
 | 
				
			||||||
use moneymgr_backend::controllers::{auth_controller, server_controller};
 | 
					use moneymgr_backend::controllers::{auth_controller, server_controller};
 | 
				
			||||||
use moneymgr_backend::routines;
 | 
					 | 
				
			||||||
use moneymgr_backend::services::users_service;
 | 
					use moneymgr_backend::services::users_service;
 | 
				
			||||||
 | 
					use moneymgr_backend::{constants, routines};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_web::main]
 | 
					#[actix_web::main]
 | 
				
			||||||
async fn main() -> std::io::Result<()> {
 | 
					async fn main() -> std::io::Result<()> {
 | 
				
			||||||
@@ -56,7 +56,7 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
        let cors = Cors::default()
 | 
					        let cors = Cors::default()
 | 
				
			||||||
            .allowed_origin(&AppConfig::get().website_origin)
 | 
					            .allowed_origin(&AppConfig::get().website_origin)
 | 
				
			||||||
            .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
 | 
					            .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
 | 
				
			||||||
            .allowed_header("X-Auth-Token")
 | 
					            .allowed_header(constants::API_TOKEN_HEADER)
 | 
				
			||||||
            .allow_any_header()
 | 
					            .allow_any_header()
 | 
				
			||||||
            .supports_credentials()
 | 
					            .supports_credentials()
 | 
				
			||||||
            .max_age(3600);
 | 
					            .max_age(3600);
 | 
				
			||||||
@@ -85,6 +85,11 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
                "/api/auth/finish_oidc",
 | 
					                "/api/auth/finish_oidc",
 | 
				
			||||||
                web::post().to(auth_controller::finish_oidc),
 | 
					                web::post().to(auth_controller::finish_oidc),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            .route("/api/auth/info", web::get().to(auth_controller::auth_info))
 | 
				
			||||||
 | 
					            .route(
 | 
				
			||||||
 | 
					                "/api/auth/sign_out",
 | 
				
			||||||
 | 
					                web::get().to(auth_controller::sign_out),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .bind(AppConfig::get().listen_address.as_str())?
 | 
					    .bind(AppConfig::get().listen_address.as_str())?
 | 
				
			||||||
    .run()
 | 
					    .run()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
import { mdiServer } from "@mdi/js";
 | 
					import { mdiCash } from "@mdi/js";
 | 
				
			||||||
import Icon from "@mdi/react";
 | 
					import Icon from "@mdi/react";
 | 
				
			||||||
import Avatar from "@mui/material/Avatar";
 | 
					import Avatar from "@mui/material/Avatar";
 | 
				
			||||||
import Box from "@mui/material/Box";
 | 
					import Box from "@mui/material/Box";
 | 
				
			||||||
@@ -68,7 +68,7 @@ export function BaseLoginPage() {
 | 
				
			|||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
 | 
					          <Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
 | 
				
			||||||
            <Icon path={mdiServer} size={1} />
 | 
					            <Icon path={mdiCash} size={1} />
 | 
				
			||||||
          </Avatar>
 | 
					          </Avatar>
 | 
				
			||||||
          <Link to="/" style={{ color: "inherit", textDecoration: "none" }}>
 | 
					          <Link to="/" style={{ color: "inherit", textDecoration: "none" }}>
 | 
				
			||||||
            <Typography component="h1" variant="h5">
 | 
					            <Typography component="h1" variant="h5">
 | 
				
			||||||
@@ -80,7 +80,7 @@ export function BaseLoginPage() {
 | 
				
			|||||||
            variant="h6"
 | 
					            variant="h6"
 | 
				
			||||||
            style={{ margin: " 40px 0px" }}
 | 
					            style={{ margin: " 40px 0px" }}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            Open source money managment
 | 
					            Open source money management
 | 
				
			||||||
          </Typography>
 | 
					          </Typography>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {/* inner page */}
 | 
					          {/* inner page */}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user