diff --git a/moneymgr_backend/src/constants.rs b/moneymgr_backend/src/constants.rs index efb31fb..96c387b 100644 --- a/moneymgr_backend/src/constants.rs +++ b/moneymgr_backend/src/constants.rs @@ -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 pub mod sessions { /// OpenID auth session state key diff --git a/moneymgr_backend/src/controllers/auth_controller.rs b/moneymgr_backend/src/controllers/auth_controller.rs index 6003b7f..102e8b7 100644 --- a/moneymgr_backend/src/controllers/auth_controller.rs +++ b/moneymgr_backend/src/controllers/auth_controller.rs @@ -1,5 +1,6 @@ use crate::app_config::AppConfig; use crate::controllers::{HttpFailure, HttpResult}; +use crate::extractors::auth_extractor::{AuthExtractor, AuthenticatedMethod}; use crate::extractors::money_session::MoneySession; use crate::services::users_service; use actix_remote_ip::RemoteIP; @@ -104,3 +105,23 @@ pub async fn finish_oidc( 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()) +} diff --git a/moneymgr_backend/src/extractors/auth_extractor.rs b/moneymgr_backend/src/extractors/auth_extractor.rs new file mode 100644 index 0000000..023000f --- /dev/null +++ b/moneymgr_backend/src/extractors/auth_extractor.rs @@ -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>; + + 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!")) + }) + } +} diff --git a/moneymgr_backend/src/extractors/mod.rs b/moneymgr_backend/src/extractors/mod.rs index 987b291..216d78a 100644 --- a/moneymgr_backend/src/extractors/mod.rs +++ b/moneymgr_backend/src/extractors/mod.rs @@ -1 +1,2 @@ +pub mod auth_extractor; pub mod money_session; diff --git a/moneymgr_backend/src/extractors/money_session.rs b/moneymgr_backend/src/extractors/money_session.rs index 4116c34..dc44e4d 100644 --- a/moneymgr_backend/src/extractors/money_session.rs +++ b/moneymgr_backend/src/extractors/money_session.rs @@ -1,5 +1,5 @@ use crate::constants; -use crate::models::users::User; +use crate::models::users::{User, UserID}; use crate::utils::rand_utils::rand_string; use actix_session::Session; use actix_web::dev::Payload; @@ -63,6 +63,17 @@ impl MoneySession { self.0.insert(constants::sessions::USER_ID, user.id())?; Ok(()) } + + /// Get current user + pub fn current_user(&self) -> anyhow::Result> { + 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 { diff --git a/moneymgr_backend/src/main.rs b/moneymgr_backend/src/main.rs index cdcf561..7dde136 100644 --- a/moneymgr_backend/src/main.rs +++ b/moneymgr_backend/src/main.rs @@ -10,8 +10,8 @@ use actix_web::{App, HttpServer, web}; use moneymgr_backend::app_config::AppConfig; use moneymgr_backend::connections::{db_connection, s3_connection}; use moneymgr_backend::controllers::{auth_controller, server_controller}; -use moneymgr_backend::routines; use moneymgr_backend::services::users_service; +use moneymgr_backend::{constants, routines}; #[actix_web::main] async fn main() -> std::io::Result<()> { @@ -56,7 +56,7 @@ async fn main() -> std::io::Result<()> { let cors = Cors::default() .allowed_origin(&AppConfig::get().website_origin) .allowed_methods(vec!["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]) - .allowed_header("X-Auth-Token") + .allowed_header(constants::API_TOKEN_HEADER) .allow_any_header() .supports_credentials() .max_age(3600); @@ -85,6 +85,11 @@ async fn main() -> std::io::Result<()> { "/api/auth/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())? .run() diff --git a/moneymgr_web/src/widgets/BaseLoginPage.tsx b/moneymgr_web/src/widgets/BaseLoginPage.tsx index dda6431..22bd569 100644 --- a/moneymgr_web/src/widgets/BaseLoginPage.tsx +++ b/moneymgr_web/src/widgets/BaseLoginPage.tsx @@ -1,4 +1,4 @@ -import { mdiServer } from "@mdi/js"; +import { mdiCash } from "@mdi/js"; import Icon from "@mdi/react"; import Avatar from "@mui/material/Avatar"; import Box from "@mui/material/Box"; @@ -68,7 +68,7 @@ export function BaseLoginPage() { }} > - + @@ -80,7 +80,7 @@ export function BaseLoginPage() { variant="h6" style={{ margin: " 40px 0px" }} > - Open source money managment + Open source money management {/* inner page */}