diff --git a/backend/src/lib/domain/warren/ports/metrics.rs b/backend/src/lib/domain/warren/ports/metrics.rs index e5e2b15..c63567c 100644 --- a/backend/src/lib/domain/warren/ports/metrics.rs +++ b/backend/src/lib/domain/warren/ports/metrics.rs @@ -60,6 +60,9 @@ pub trait WarrenMetrics: Clone + Send + Sync + 'static { fn record_warren_share_cat_success(&self) -> impl Future + Send; fn record_warren_share_cat_failure(&self) -> impl Future + Send; + + fn record_warren_share_password_verification_success(&self) -> impl Future + Send; + fn record_warren_share_password_verification_failure(&self) -> impl Future + Send; } pub trait FileSystemMetrics: Clone + Send + Sync + 'static { diff --git a/backend/src/lib/domain/warren/ports/mod.rs b/backend/src/lib/domain/warren/ports/mod.rs index 8edf098..1308d89 100644 --- a/backend/src/lib/domain/warren/ports/mod.rs +++ b/backend/src/lib/domain/warren/ports/mod.rs @@ -25,6 +25,7 @@ use super::models::{ DeleteShareError, DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, GetShareResponse, ListSharesError, ListSharesRequest, ListSharesResponse, ShareCatError, ShareCatRequest, ShareCatResponse, ShareLsError, ShareLsRequest, ShareLsResponse, + VerifySharePasswordError, VerifySharePasswordRequest, VerifySharePasswordResponse, }, user::{ CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError, @@ -138,6 +139,10 @@ pub trait WarrenService: Clone + Send + Sync + 'static { &self, request: ShareCatRequest, ) -> impl Future> + Send; + fn verify_warren_share_password( + &self, + request: VerifySharePasswordRequest, + ) -> impl Future> + Send; } pub trait FileSystemService: Clone + Send + Sync + 'static { diff --git a/backend/src/lib/domain/warren/ports/notifier.rs b/backend/src/lib/domain/warren/ports/notifier.rs index f92033e..1951fc7 100644 --- a/backend/src/lib/domain/warren/ports/notifier.rs +++ b/backend/src/lib/domain/warren/ports/notifier.rs @@ -5,7 +5,7 @@ use crate::domain::warren::models::{ file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse}, share::{ CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse, - ShareCatResponse, ShareLsResponse, + ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse, }, user::{ListAllUsersAndWarrensResponse, LoginUserOidcResponse, LoginUserResponse, User}, user_warren::UserWarren, @@ -64,6 +64,10 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static { ) -> impl Future + Send; fn warren_share_ls(&self, response: &ShareLsResponse) -> impl Future + Send; fn warren_share_cat(&self, response: &ShareCatResponse) -> impl Future + Send; + fn warren_share_password_verified( + &self, + response: &VerifySharePasswordResponse, + ) -> impl Future + Send; } pub trait FileSystemNotifier: Clone + Send + Sync + 'static { diff --git a/backend/src/lib/domain/warren/service/warren.rs b/backend/src/lib/domain/warren/service/warren.rs index f8682d1..3020f2f 100644 --- a/backend/src/lib/domain/warren/service/warren.rs +++ b/backend/src/lib/domain/warren/service/warren.rs @@ -6,7 +6,8 @@ use crate::domain::warren::{ DeleteShareRequest, DeleteShareResponse, GetShareError, GetShareRequest, GetShareResponse, ListSharesError, ListSharesRequest, ListSharesResponse, Share, ShareCatError, ShareCatRequest, ShareCatResponse, ShareLsError, ShareLsRequest, - ShareLsResponse, + ShareLsResponse, VerifySharePasswordError, VerifySharePasswordRequest, + VerifySharePasswordResponse, }, warren::{ CreateWarrenError, CreateWarrenRequest, DeleteWarrenError, DeleteWarrenRequest, @@ -528,4 +529,24 @@ where Ok(response) } + + async fn verify_warren_share_password( + &self, + request: VerifySharePasswordRequest, + ) -> Result { + let result = self.repository.verify_warren_share_password(request).await; + + if let Ok(response) = result.as_ref() { + self.metrics + .record_warren_share_password_verification_success() + .await; + self.notifier.warren_share_password_verified(response).await; + } else { + self.metrics + .record_warren_share_password_verification_failure() + .await; + } + + result + } } diff --git a/backend/src/lib/inbound/http/handlers/warrens/ls_share.rs b/backend/src/lib/inbound/http/handlers/warrens/ls_share.rs index cabc758..c7e1c73 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/ls_share.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/ls_share.rs @@ -13,6 +13,7 @@ use crate::{ }, inbound::http::{ AppState, + handlers::extractors::SharePasswordHeader, responses::{ApiError, ApiSuccess}, }, }; @@ -59,17 +60,14 @@ impl From for ApiError { pub(super) struct LsShareHttpRequestBody { share_id: Uuid, path: String, - password: Option, } impl LsShareHttpRequestBody { - fn try_into_domain(self) -> Result { + fn try_into_domain( + self, + password: Option, + ) -> Result { let path = FilePath::new(&self.path)?.try_into()?; - let password = if let Some(password) = self.password.as_ref() { - Some(SharePassword::new(password)?) - } else { - None - }; Ok(ShareLsRequest::new(self.share_id, path, password)) } @@ -98,9 +96,10 @@ impl From for ShareLsResponseData { pub async fn ls_share( State(state): State>, + SharePasswordHeader(password): SharePasswordHeader, Json(request): Json, ) -> Result, ApiError> { - let domain_request = request.try_into_domain()?; + let domain_request = request.try_into_domain(password)?; state .warren_service diff --git a/backend/src/lib/inbound/http/handlers/warrens/mod.rs b/backend/src/lib/inbound/http/handlers/warrens/mod.rs index ca5251a..a96d08a 100644 --- a/backend/src/lib/inbound/http/handlers/warrens/mod.rs +++ b/backend/src/lib/inbound/http/handlers/warrens/mod.rs @@ -7,6 +7,7 @@ mod list_shares; mod list_warrens; mod ls_share; mod upload_warren_files; +mod verify_share_password; mod warren_cat; mod warren_cp; mod warren_ls; @@ -37,6 +38,7 @@ use get_share::get_share; use list_shares::list_shares; use ls_share::ls_share; use upload_warren_files::warren_save; +use verify_share_password::verify_share_password; use warren_cat::fetch_file; use warren_cp::warren_cp; use warren_mv::warren_mv; @@ -116,4 +118,5 @@ pub fn routes() -> Router> .route("/files/get_share", post(get_share)) .route("/files/ls_share", post(ls_share)) .route("/files/cat_share", get(cat_share)) + .route("/files/verify_share_password", post(verify_share_password)) } diff --git a/backend/src/lib/inbound/http/handlers/warrens/verify_share_password.rs b/backend/src/lib/inbound/http/handlers/warrens/verify_share_password.rs new file mode 100644 index 0000000..f779a06 --- /dev/null +++ b/backend/src/lib/inbound/http/handlers/warrens/verify_share_password.rs @@ -0,0 +1,72 @@ +use axum::{Json, extract::State, http::StatusCode}; +use serde::Deserialize; +use thiserror::Error; +use uuid::Uuid; + +use crate::{ + domain::warren::{ + models::share::{SharePassword, SharePasswordError, VerifySharePasswordRequest}, + ports::{AuthService, WarrenService}, + }, + inbound::http::{ + AppState, + responses::{ApiError, ApiSuccess}, + }, +}; + +#[derive(Debug, Error)] +enum ParseVerifySharePasswordHttpRequestError { + #[error(transparent)] + SharePassword(#[from] SharePasswordError), +} + +impl From for ApiError { + fn from(value: ParseVerifySharePasswordHttpRequestError) -> Self { + match value { + ParseVerifySharePasswordHttpRequestError::SharePassword(err) => Self::BadRequest( + match err { + SharePasswordError::Empty => "The provided password is empty", + SharePasswordError::LeadingWhitespace + | SharePasswordError::TrailingWhitespace + | SharePasswordError::TooShort + | SharePasswordError::TooLong => "", + } + .to_string(), + ), + } + } +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(super) struct VerifySharePasswordHttpRequestBody { + share_id: Uuid, + password: String, +} + +impl VerifySharePasswordHttpRequestBody { + fn try_into_domain( + self, + ) -> Result { + let password = SharePassword::new(&self.password)?; + + Ok(VerifySharePasswordRequest::new( + self.share_id, + Some(password), + )) + } +} + +pub async fn verify_share_password( + State(state): State>, + Json(request): Json, +) -> Result, ApiError> { + let domain_request = request.try_into_domain()?; + + state + .warren_service + .verify_warren_share_password(domain_request) + .await + .map(|_| ApiSuccess::new(StatusCode::OK, ())) + .map_err(ApiError::from) +} diff --git a/backend/src/lib/outbound/metrics_debug_logger.rs b/backend/src/lib/outbound/metrics_debug_logger.rs index 3e2134a..1ed7b5f 100644 --- a/backend/src/lib/outbound/metrics_debug_logger.rs +++ b/backend/src/lib/outbound/metrics_debug_logger.rs @@ -151,6 +151,13 @@ impl WarrenMetrics for MetricsDebugLogger { async fn record_warren_share_cat_failure(&self) { tracing::debug!("[Metrics] Warren share cat failed"); } + + async fn record_warren_share_password_verification_success(&self) { + tracing::debug!("[Metrics] Warren share password verification succeeded"); + } + async fn record_warren_share_password_verification_failure(&self) { + tracing::debug!("[Metrics] Warren share password verification failed"); + } } impl FileSystemMetrics for MetricsDebugLogger { diff --git a/backend/src/lib/outbound/notifier_debug_logger.rs b/backend/src/lib/outbound/notifier_debug_logger.rs index 2755645..aa49796 100644 --- a/backend/src/lib/outbound/notifier_debug_logger.rs +++ b/backend/src/lib/outbound/notifier_debug_logger.rs @@ -11,7 +11,7 @@ use crate::domain::{ file::{AbsoluteFilePath, AbsoluteFilePathList, LsResponse}, share::{ CreateShareResponse, DeleteShareResponse, GetShareResponse, ListSharesResponse, - ShareCatResponse, ShareLsResponse, + ShareCatResponse, ShareLsResponse, VerifySharePasswordResponse, }, user::{ ListAllUsersAndWarrensResponse, LoginUserOidcResponse, LoginUserResponse, User, @@ -201,6 +201,13 @@ impl WarrenNotifier for NotifierDebugLogger { response.share().id(), ); } + + async fn warren_share_password_verified(&self, response: &VerifySharePasswordResponse) { + tracing::debug!( + "[Notifier] Verified password for share {}", + response.share().id() + ); + } } impl FileSystemNotifier for NotifierDebugLogger { diff --git a/frontend/lib/api/shares.ts b/frontend/lib/api/shares.ts index 959f2f2..3ab81be 100644 --- a/frontend/lib/api/shares.ts +++ b/frontend/lib/api/shares.ts @@ -120,19 +120,24 @@ export async function listShareFiles( | { success: true; files: DirectoryEntry[]; parent: DirectoryEntry | null } | { success: false } > { - const { data } = await useFetch< + const { data, error } = await useFetch< ApiResponse<{ files: DirectoryEntry[]; parent: DirectoryEntry | null }> >(getApiUrl('warrens/files/ls_share'), { method: 'POST', - headers: getApiHeaders(), + // This is only required for development + headers: + password != null + ? { ...getApiHeaders(), 'X-Share-Password': password } + : getApiHeaders(), body: JSON.stringify({ shareId: shareId, path: path, - password: password, }), }); if (data.value == null) { + const errorMessage = await error.value?.data; + console.log(errorMessage); return { success: false, }; @@ -174,3 +179,32 @@ export async function fetchShareFile( data: data.value, }; } + +export async function verifySharePassword( + shareId: string, + password: string +): Promise<{ success: boolean }> { + const { data } = await useFetch>( + getApiUrl(`warrens/files/verify_share_password`), + { + method: 'POST', + headers: getApiHeaders(), + body: JSON.stringify({ + shareId: shareId, + password: password, + }), + responseType: 'json', + cache: 'default', + } + ); + + if (data.value == null || data.value.status !== 200) { + return { + success: false, + }; + } + + return { + success: true, + }; +} diff --git a/frontend/pages/share.vue b/frontend/pages/share.vue index 0e45176..1289a93 100644 --- a/frontend/pages/share.vue +++ b/frontend/pages/share.vue @@ -1,5 +1,12 @@