diff --git a/backend/.sqlx/query-0d75c9bd7f532622c80cc856e9ee48923a97b5db7130e6517bee3ea90cc24ef5.json b/backend/.sqlx/query-0d75c9bd7f532622c80cc856e9ee48923a97b5db7130e6517bee3ea90cc24ef5.json new file mode 100644 index 0000000000..469f27705c --- /dev/null +++ b/backend/.sqlx/query-0d75c9bd7f532622c80cc856e9ee48923a97b5db7130e6517bee3ea90cc24ef5.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT email FROM password WHERE username = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "0d75c9bd7f532622c80cc856e9ee48923a97b5db7130e6517bee3ea90cc24ef5" +} diff --git a/backend/.sqlx/query-68c19cb0e18b94870bbe81f9aab92ba37da67cd2a56834c9d1378eab7551284d.json b/backend/.sqlx/query-68c19cb0e18b94870bbe81f9aab92ba37da67cd2a56834c9d1378eab7551284d.json deleted file mode 100644 index de69c0fc8c..0000000000 --- a/backend/.sqlx/query-68c19cb0e18b94870bbe81f9aab92ba37da67cd2a56834c9d1378eab7551284d.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n filters = $4,\n filter_logic = $5,\n auto_offset_reset = $6,\n auto_commit = $7,\n script_path = $8,\n path = $9,\n is_flow = $10,\n edited_by = $11,\n permissioned_as = $12,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $15,\n error_handler_args = $16,\n retry = $17\n WHERE\n workspace_id = $13 AND path = $14\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "VarcharArray", - "JsonbArray", - "Varchar", - "Varchar", - "Bool", - "Varchar", - "Varchar", - "Bool", - "Varchar", - "Varchar", - "Text", - "Text", - "Varchar", - "Jsonb", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "68c19cb0e18b94870bbe81f9aab92ba37da67cd2a56834c9d1378eab7551284d" -} diff --git a/backend/.sqlx/query-66e2f8468ba64f22b7a7caa18639d7c833ac2ec573bd89d878b5c8b1afc74d3a.json b/backend/.sqlx/query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json similarity index 54% rename from backend/.sqlx/query-66e2f8468ba64f22b7a7caa18639d7c833ac2ec573bd89d878b5c8b1afc74d3a.json rename to backend/.sqlx/query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json index 15681aab51..7c8a5bc156 100644 --- a/backend/.sqlx/query-66e2f8468ba64f22b7a7caa18639d7c833ac2ec573bd89d878b5c8b1afc74d3a.json +++ b/backend/.sqlx/query-7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n workspace_id,\n auto_invite->'instance_groups_roles' as instance_groups_roles,\n auto_invite->'instance_groups' as instance_groups_json\n FROM workspace_settings\n WHERE\n auto_invite->'instance_groups' IS NOT NULL\n AND auto_invite->'instance_groups' ? $1\n ", + "query": "\n SELECT\n workspace_id,\n auto_invite->'instance_groups_roles' as instance_groups_roles\n FROM workspace_settings\n WHERE\n auto_invite->'instance_groups' IS NOT NULL\n AND auto_invite->'instance_groups' ? $1\n ", "describe": { "columns": [ { @@ -12,11 +12,6 @@ "ordinal": 1, "name": "instance_groups_roles", "type_info": "Jsonb" - }, - { - "ordinal": 2, - "name": "instance_groups_json", - "type_info": "Jsonb" } ], "parameters": { @@ -26,9 +21,8 @@ }, "nullable": [ false, - null, null ] }, - "hash": "66e2f8468ba64f22b7a7caa18639d7c833ac2ec573bd89d878b5c8b1afc74d3a" + "hash": "7e01ef5799168c0fc2779d42ce352827e2fda6711c0a1b104ca6435ddb14b47d" } diff --git a/backend/.sqlx/query-6a8f4ed9946bb2a3c5e90695c90b70aa2e83fcb5aa0c953febdd9bac2d95bbec.json b/backend/.sqlx/query-a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2.json similarity index 57% rename from backend/.sqlx/query-6a8f4ed9946bb2a3c5e90695c90b70aa2e83fcb5aa0c953febdd9bac2d95bbec.json rename to backend/.sqlx/query-a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2.json index c6d3374639..99d8d2a181 100644 --- a/backend/.sqlx/query-6a8f4ed9946bb2a3c5e90695c90b70aa2e83fcb5aa0c953febdd9bac2d95bbec.json +++ b/backend/.sqlx/query-a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO kafka_trigger (\n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n filters,\n filter_logic,\n auto_offset_reset,\n auto_commit,\n script_path,\n is_flow,\n mode,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, now(), $15, $16, $17\n )\n ", + "query": "\n INSERT INTO kafka_trigger (\n workspace_id,\n path,\n kafka_resource_path,\n group_id,\n topics,\n filters,\n auto_offset_reset,\n auto_commit,\n script_path,\n is_flow,\n mode,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, now(), $14, $15, $16\n )\n ", "describe": { "columns": [], "parameters": { @@ -12,7 +12,6 @@ "VarcharArray", "JsonbArray", "Varchar", - "Varchar", "Bool", "Varchar", "Bool", @@ -37,5 +36,5 @@ }, "nullable": [] }, - "hash": "6a8f4ed9946bb2a3c5e90695c90b70aa2e83fcb5aa0c953febdd9bac2d95bbec" + "hash": "a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2" } diff --git a/backend/.sqlx/query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json b/backend/.sqlx/query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json new file mode 100644 index 0000000000..c993120dae --- /dev/null +++ b/backend/.sqlx/query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json @@ -0,0 +1,29 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE kafka_trigger\n SET\n kafka_resource_path = $1,\n group_id = $2,\n topics = $3,\n filters = $4,\n auto_offset_reset = $5,\n auto_commit = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n edited_by = $10,\n permissioned_as = $11,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND path = $13\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "VarcharArray", + "JsonbArray", + "Varchar", + "Bool", + "Varchar", + "Varchar", + "Bool", + "Varchar", + "Varchar", + "Text", + "Text", + "Varchar", + "Jsonb", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7" +} diff --git a/backend/migrations/20260401000000_fix_trigger_permissioned_as.down.sql b/backend/migrations/20260401000000_fix_trigger_permissioned_as.down.sql new file mode 100644 index 0000000000..a3fd1060ab --- /dev/null +++ b/backend/migrations/20260401000000_fix_trigger_permissioned_as.down.sql @@ -0,0 +1 @@ +-- No-op: the forward migration only fixes stale data; reverting is not meaningful. diff --git a/backend/migrations/20260401000000_fix_trigger_permissioned_as.up.sql b/backend/migrations/20260401000000_fix_trigger_permissioned_as.up.sql new file mode 100644 index 0000000000..a00eb8de7b --- /dev/null +++ b/backend/migrations/20260401000000_fix_trigger_permissioned_as.up.sql @@ -0,0 +1,103 @@ +-- Fix triggers/schedules where permissioned_as = 'u/{username}' but the user +-- does not exist in the workspace's usr table. Replace with the raw email +-- from the instance-level password table so the super-admin check succeeds. + +UPDATE http_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE websocket_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE postgres_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE mqtt_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE kafka_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE nats_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE sqs_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE gcp_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE email_trigger t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); + +UPDATE schedule t SET permissioned_as = p.email +FROM password p +WHERE t.permissioned_as LIKE 'u/%' + AND p.username = SUBSTRING(t.permissioned_as FROM 3) + AND NOT EXISTS ( + SELECT 1 FROM usr u + WHERE u.username = SUBSTRING(t.permissioned_as FROM 3) + AND u.workspace_id = t.workspace_id + ); diff --git a/backend/windmill-common/src/users.rs b/backend/windmill-common/src/users.rs index f91c21701a..cf5ff27137 100644 --- a/backend/windmill-common/src/users.rs +++ b/backend/windmill-common/src/users.rs @@ -68,8 +68,20 @@ pub async fn get_email_from_permissioned_as( workspace_id ) .fetch_optional(db) - .await? - .unwrap_or_else(|| format!("{}@unknown.windmill.dev", username)); + .await?; + let email = match email { + Some(e) => e, + None => { + // User not in workspace — check instance-level password table. + // This handles super admins who can access any workspace without + // a usr record; without the real email, the super_admin check in + // fetch_authed_from_permissioned_as would fail. + sqlx::query_scalar!("SELECT email FROM password WHERE username = $1", username) + .fetch_optional(db) + .await? + .unwrap_or_else(|| format!("{}@unknown.windmill.dev", username)) + } + }; let key = (workspace_id.to_string(), username.to_string()); EMAIL_CACHE.insert(key, (email.clone(), std::time::Instant::now())); Ok(email)