diff --git a/backend/windmill-api-workspaces/src/workspaces.rs b/backend/windmill-api-workspaces/src/workspaces.rs index f29fd00a58..477ab939ce 100644 --- a/backend/windmill-api-workspaces/src/workspaces.rs +++ b/backend/windmill-api-workspaces/src/workspaces.rs @@ -6460,10 +6460,14 @@ async fn clone_apps( if !preserve_identity { downgrade_cloned_app_policy(&mut app.policy, authed); } - // Off cloud a custom path addresses an app instance-wide (`create_app` rejects one - // already taken in any workspace), so a clone that kept it would make the parent's - // live public URL resolve to either row. Cloud scopes the lookup by workspace. - let custom_path = if *CLOUD_HOSTED { app.custom_path } else { None }; + // An instance-wide custom path addresses one app (`create_app` rejects one already + // taken in any workspace), so a clone that kept it would make the parent's live + // public URL resolve to either row. + let custom_path = if windmill_common::apps::custom_path_is_workspace_scoped() { + app.custom_path + } else { + None + }; let new_app_id = sqlx::query_scalar!( "INSERT INTO app (workspace_id, path, summary, policy, versions, extra_perms, custom_path) VALUES ($1, $2, $3, $4, $5, $6, $7) diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index 037f701346..35741c6cc2 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -56,7 +56,7 @@ use std::str; use windmill_audit::audit_oss::{audit_log, AuditAuthorable}; use windmill_audit::ActionKind; use windmill_common::{ - apps::{AppScriptId, ListAppQuery, APP_WORKSPACED_ROUTE}, + apps::{AppScriptId, ListAppQuery}, auth::TOKEN_PREFIX_LEN, cache::{self, future::FutureCachedExt}, db::{DbWithOptAuthed, UserDB}, @@ -1114,13 +1114,13 @@ async fn custom_path_exists( Extension(db): Extension, Path((w_id, custom_path)): Path<(String, String)>, ) -> JsonResult { - let as_workspaced_route = APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed); + let scoped = windmill_common::apps::custom_path_is_workspace_scoped(); let exists = sqlx::query_scalar!( "SELECT EXISTS(SELECT 1 FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2))", custom_path, - if *CLOUD_HOSTED || as_workspaced_route { Some(&w_id) } else { None } + if scoped { Some(&w_id) } else { None } ) .fetch_one(&db) .await?.unwrap_or(false); @@ -2179,8 +2179,7 @@ async fn create_app_internal<'a>( } if let Some(custom_path) = &app.custom_path { require_admin(authed.is_admin, &authed.username)?; - let scoped = - *CLOUD_HOSTED || APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed); + let scoped = windmill_common::apps::custom_path_is_workspace_scoped(); let conflict = sqlx::query!( "SELECT workspace_id, path FROM app WHERE custom_path = $1 AND ($2::TEXT IS NULL OR workspace_id = $2) LIMIT 1", @@ -3055,8 +3054,7 @@ async fn update_app_internal<'a>( if let Some(ncustom_path) = &ns.custom_path { require_admin(authed.is_admin, &authed.username)?; - let scoped = - *CLOUD_HOSTED || APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed); + let scoped = windmill_common::apps::custom_path_is_workspace_scoped(); if ncustom_path.is_empty() { sqlb.set("custom_path", "NULL"); diff --git a/backend/windmill-common/src/apps.rs b/backend/windmill-common/src/apps.rs index 36b2d9b704..1f53cee9d0 100644 --- a/backend/windmill-common/src/apps.rs +++ b/backend/windmill-common/src/apps.rs @@ -18,6 +18,13 @@ lazy_static::lazy_static! { pub static ref APP_WORKSPACED_ROUTE: AtomicBool = AtomicBool::new(false); } +/// Whether an app's custom path names it within its workspace rather than instance-wide. +/// Every site that stores or resolves one must agree: a path stored under a narrower scope +/// than the resolver's makes two apps answer the same public URL. +pub fn custom_path_is_workspace_scoped() -> bool { + *crate::worker::CLOUD_HOSTED || APP_WORKSPACED_ROUTE.load(std::sync::atomic::Ordering::Relaxed) +} + /// Traverse FlowValue while invoking provided by caller callback on leafs // #[async_recursion::async_recursion(?Send)] pub fn traverse_app_inline_scripts<