diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 0b3fc15..8961ede 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -144,6 +144,7 @@ dependencies = [ "axum", "axum-core", "bytes", + "cookie", "fastrand", "futures-util", "http", @@ -292,6 +293,17 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 829a79b..8522e6d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -15,7 +15,7 @@ path = "src/bin/backend/main.rs" anyhow = "1.0.98" argon2 = "0.5.3" axum = { version = "0.8.4", features = ["multipart", "query"] } -axum-extra = { version = "0.10.1", features = ["multipart"] } +axum-extra = { version = "0.10.1", features = ["cookie", "multipart"] } base64 = "0.22.1" bytes = "1.10.1" chrono = "0.4.41" diff --git a/backend/src/lib/inbound/http/handlers/extractors.rs b/backend/src/lib/inbound/http/handlers/extractors.rs index 938520d..1e58136 100644 --- a/backend/src/lib/inbound/http/handlers/extractors.rs +++ b/backend/src/lib/inbound/http/handlers/extractors.rs @@ -2,6 +2,7 @@ use axum::{ extract::FromRequestParts, http::{header::AUTHORIZATION, request::Parts}, }; +use axum_extra::extract::CookieJar; use crate::{ domain::warren::models::auth_session::AuthSessionIdWithType, inbound::http::responses::ApiError, @@ -15,21 +16,38 @@ where { type Rejection = ApiError; - async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - let Some(header) = parts.headers.get(AUTHORIZATION) else { - return Err(ApiError::Unauthorized( - "Missing authorization header".to_string(), - )); - }; + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + // Release build + if !cfg!(debug_assertions) { + let jar = CookieJar::from_request_parts(parts, state).await.unwrap(); - let header_str = header.to_str().map_err(|_| { - ApiError::InternalServerError( - "Failed to get authorization header as string".to_string(), - ) - })?; + let Some(cookie) = jar.get(AUTHORIZATION.as_str()) else { + return Err(ApiError::Unauthorized( + "Missing authorization cookie".to_string(), + )); + }; - AuthSessionIdWithType::from_str(header_str) - .map(|session_id| SessionIdHeader(session_id)) - .map_err(|_| ApiError::BadRequest("Invalid session id".to_string())) + AuthSessionIdWithType::from_str(cookie.value()) + .map(|session_id| SessionIdHeader(session_id)) + .map_err(|_| ApiError::BadRequest("Invalid session id".to_string())) + } + // Debug build + else { + let Some(header) = parts.headers.get(AUTHORIZATION) else { + return Err(ApiError::Unauthorized( + "Missing authorization header".to_string(), + )); + }; + + let header_str = header.to_str().map_err(|_| { + ApiError::InternalServerError( + "Failed to get authorization header as string".to_string(), + ) + })?; + + AuthSessionIdWithType::from_str(header_str) + .map(|session_id| SessionIdHeader(session_id)) + .map_err(|_| ApiError::BadRequest("Invalid session id".to_string())) + } } } diff --git a/compose.yaml b/compose.yaml index 74641f1..86a1620 100644 --- a/compose.yaml +++ b/compose.yaml @@ -18,6 +18,7 @@ services: - 'SERVE_DIRECTORY=/serve' - 'CORS_ALLOW_ORIGIN=http://localhost:8081' - 'LOG_LEVEL=debug' + - 'MAX_FILE_FETCH_BYTES=10737418240' volumes: - './backend/serve:/serve:rw' postgres: diff --git a/frontend/.env.dev b/frontend/.env.dev index b46e796..a26ef6a 100644 --- a/frontend/.env.dev +++ b/frontend/.env.dev @@ -1,5 +1,5 @@ # this file is ignored when the app is built since we're using SSG NUXT_PUBLIC_API_BASE="http://127.0.0.1:8080/api" -NUXT_COOKIES_SECURE="false" -NUXT_COOKIES_SAME_SITE="strict" +NUXT_PUBLIC_COOKIES_SECURE="false" +NUXT_PUBLIC_COOKIES_SAME_SITE="strict" diff --git a/frontend/components/DirectoryEntry.vue b/frontend/components/DirectoryEntry.vue index 06fe1c1..c5d4028 100644 --- a/frontend/components/DirectoryEntry.vue +++ b/frontend/components/DirectoryEntry.vue @@ -12,6 +12,7 @@ import { fetchFile, } from '~/lib/api/warrens'; import type { DirectoryEntry } from '#shared/types'; +import { toast } from 'vue-sonner'; const warrenStore = useWarrenStore(); const copyStore = useCopyStore(); @@ -110,6 +111,30 @@ function onCopy() { entry.name ); } + +async function onDownload() { + if (warrenStore.current == null) { + return; + } + + if (entry.fileType !== 'file') { + toast.warning('Download', { + description: 'Directory downloads are not supported yet', + }); + return; + } + + const anchor = document.createElement('a'); + + anchor.download = entry.name; + anchor.href = getApiUrl( + `warrens/files/cat?warrenId=${warrenStore.current.warrenId}&path=${joinPaths(warrenStore.current.path, entry.name)}` + ); + anchor.rel = 'noopener'; + anchor.target = '_blank'; + + anchor.click(); +}