diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 538ef8ce8c..f18eb8a7c1 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -20973,6 +20973,10 @@ components: type: boolean custom_path: type: string + raw_app: + type: boolean + bundle_secret: + type: string required: - id - workspace_id @@ -20985,6 +20989,7 @@ components: - policy - execution_mode - extra_perms + - raw_app AppWithLastVersionWDraft: allOf: diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 56f604f0e0..f013db7338 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -120,6 +120,7 @@ pub fn unauthed_service() -> Router { .route("/download_s3_file/*path", get(download_s3_file_from_app)) .route("/public_app/:secret", get(get_public_app_by_secret)) .route("/public_resource/*path", get(get_public_resource)) + .route("/get_data/v/*id", get(get_raw_app_data)) } pub fn global_service() -> Router { Router::new() @@ -175,6 +176,9 @@ pub struct AppWithLastVersion { #[serde(skip_serializing_if = "Option::is_none")] pub custom_path: Option, pub raw_app: bool, + #[sqlx(skip)] + #[serde(skip_serializing_if = "Option::is_none")] + pub bundle_secret: Option, } #[derive(Serialize, FromRow)] @@ -770,7 +774,7 @@ async fn get_public_app_by_secret( "SELECT app.id, app.path, app.summary, app.versions, app.policy, app.custom_path, null as extra_perms, coalesce(app_version_lite.value::json, app_version.value::json) as value, app_version.created_at, app_version.created_by, app_version.raw_app - FROM app, app_version + FROM app, app_version LEFT JOIN app_version_lite ON app_version_lite.id = app_version.id WHERE app.id = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]") .bind(&id) @@ -778,36 +782,37 @@ async fn get_public_app_by_secret( .fetch_optional(&db) .await?; - let app = not_found_if_none(app_o, "App", id.to_string())?; + let mut app = not_found_if_none(app_o, "App", id.to_string())?; let policy = serde_json::from_str::(app.policy.0.get()).map_err(to_anyhow)?; - if matches!(policy.execution_mode, ExecutionMode::Anonymous) { - return Ok(Json(app)); - } - - if opt_authed.is_none() { - { + if !matches!(policy.execution_mode, ExecutionMode::Anonymous) { + if opt_authed.is_none() { return Err(Error::NotAuthorized( "App visibility does not allow public access and you are not logged in".to_string(), )); + } else { + let authed = opt_authed.unwrap(); + let mut tx = user_db.begin(&authed).await?; + let is_visible = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM app WHERE id = $1 AND workspace_id = $2)", + id, + &w_id + ) + .fetch_one(&mut *tx) + .await?; + tx.commit().await?; + if !is_visible.unwrap_or(false) { + return Err(Error::NotAuthorized( + "App visibility does not allow public access and you are logged in but you have no read-access to that app".to_string(), + )); + } } - } else { - let authed = opt_authed.unwrap(); - let mut tx = user_db.begin(&authed).await?; - let is_visible = sqlx::query_scalar!( - "SELECT EXISTS(SELECT 1 FROM app WHERE id = $1 AND workspace_id = $2)", - id, - &w_id - ) - .fetch_one(&mut *tx) - .await?; - tx.commit().await?; - if !is_visible.unwrap_or(false) { - return Err(Error::NotAuthorized( - "App visibility does not allow public access and you are logged in but you have no read-access to that app".to_string(), - )); - } + } + + // Compute bundle_secret for raw apps + if app.raw_app { + app.bundle_secret = Some(compute_bundle_secret(&db, &w_id, &app.versions).await?); } Ok(Json(app)) @@ -892,6 +897,15 @@ async fn get_secret_id( const BUNDLE_SECRET_PREFIX: &str = "bundle_"; +pub async fn compute_bundle_secret(db: &DB, w_id: &str, versions: &[i64]) -> Result { + let version_id = versions + .last() + .ok_or_else(|| Error::internal_err("App has no versions".to_string()))?; + let mc = build_crypt(db, w_id).await?; + let hx = hex::encode(mc.encrypt_str_to_bytes(format!("{}{}", BUNDLE_SECRET_PREFIX, version_id))); + Ok(hx) +} + async fn get_latest_version_secret_id( authed: ApiAuthed, Extension(user_db): Extension, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cb88b47ffb..44686df50c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -835,7 +835,6 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -847,7 +846,6 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -858,7 +856,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1348,7 +1345,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.0.tgz", "integrity": "sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1550,7 +1546,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1567,7 +1562,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1584,7 +1578,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1601,7 +1594,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1618,7 +1610,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1635,7 +1626,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1652,7 +1642,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1669,7 +1658,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1686,7 +1674,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1703,7 +1690,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1720,7 +1706,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1737,7 +1722,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1754,7 +1738,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2375,7 +2358,6 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -7214,7 +7196,7 @@ "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -7650,7 +7632,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7671,7 +7652,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7692,7 +7672,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7713,7 +7692,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7734,7 +7712,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7755,7 +7732,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7776,7 +7752,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7797,7 +7772,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7818,7 +7792,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7839,7 +7812,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -7860,7 +7832,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -12498,21 +12469,6 @@ } } }, - "node_modules/svelte-check/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/svelte-eslint-parser": { "version": "0.43.0", "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.43.0.tgz", diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte index 6bcdf298f8..76e74aa2e4 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeaderDeploy.svelte @@ -43,7 +43,7 @@ pathError: string newEditedPath: string newPath: string - hideSecretUrl: boolean + hideSecretUrl?: boolean } = $props() let dirtyCustomPath = $state(false) let path: Path | undefined = $state(undefined) diff --git a/frontend/src/lib/components/apps/editor/PublicApp.svelte b/frontend/src/lib/components/apps/editor/PublicApp.svelte index 9495030573..16758983f9 100644 --- a/frontend/src/lib/components/apps/editor/PublicApp.svelte +++ b/frontend/src/lib/components/apps/editor/PublicApp.svelte @@ -13,6 +13,8 @@ import { urlParamsToObject } from '$lib/utils' import { goto } from '$app/navigation' import AppPreview from './AppPreview.svelte' + import RawAppPreview from '$lib/components/raw_apps/RawAppPreview.svelte' + import type { Runnable } from '$lib/components/raw_apps/rawAppPolicy' import { twMerge } from 'tailwind-merge' import { writable } from 'svelte/store' @@ -28,10 +30,13 @@ noPermission: boolean jwtError: boolean onLoginSuccess: () => void - app: (AppWithLastVersion & { value: any }) | undefined + app: (AppWithLastVersion & { value: any; workspace_id?: string }) | undefined workspace: string | undefined } = $props() + // Use workspace from props or from app.workspace_id (for custom path responses) + let effectiveWorkspace = $derived(workspace ?? app?.workspace_id) + setContext(IS_APP_PUBLIC_CONTEXT_KEY, true) const breakpoint = writable('lg') @@ -97,9 +102,9 @@ {:else if noPermission}
This app requires read access
- {#if $userStore}You are logged in but have no read access to this app{:else if globalUser && workspace} + {#if $userStore}You are logged in but have no read access to this app{:else if globalUser && effectiveWorkspace} You are logged in but are not a member of the workspace {workspace}{effectiveWorkspace} this app is part of {:else}You must be logged in and have read access to this app{/if}
@@ -110,35 +115,51 @@ {:else if app} {#key app} -
- goto(path)} - gotoFn={(path, opt) => goto(path, opt)} + {#if app.raw_app && effectiveWorkspace} + } /> -
+ {:else if app.raw_app && !effectiveWorkspace} +
+ + Unable to load raw app: workspace information is missing. + +
+ {:else} +
+ goto(path)} + gotoFn={(path, opt) => goto(path, opt)} + /> +
+ {/if} {/key} {:else} diff --git a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte index 7232318d8c..b732af8979 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte @@ -718,7 +718,6 @@ bind:customPathError bind:pathError bind:newEditedPath - hideSecretUrl={true} /> diff --git a/frontend/src/lib/components/raw_apps/utils.ts b/frontend/src/lib/components/raw_apps/utils.ts index f57a7e3a32..077a9ddad8 100644 --- a/frontend/src/lib/components/raw_apps/utils.ts +++ b/frontend/src/lib/components/raw_apps/utils.ts @@ -27,7 +27,7 @@ export function htmlContent( App Preview - + + ` }