diff --git a/backend/.sqlx/query-1d4bb4f53574ef95ef1016b760f849ec2372ac6a21bb2556d17a96dc72ea4980.json b/backend/.sqlx/query-02748cae17e8966dbd57a33017ccb747c84fcc12fbfd93c6c749570b94d35696.json similarity index 68% rename from backend/.sqlx/query-1d4bb4f53574ef95ef1016b760f849ec2372ac6a21bb2556d17a96dc72ea4980.json rename to backend/.sqlx/query-02748cae17e8966dbd57a33017ccb747c84fcc12fbfd93c6c749570b94d35696.json index 2257984d66..04c3abdf0e 100644 --- a/backend/.sqlx/query-1d4bb4f53574ef95ef1016b760f849ec2372ac6a21bb2556d17a96dc72ea4980.json +++ b/backend/.sqlx/query-02748cae17e8966dbd57a33017ccb747c84fcc12fbfd93c6c749570b94d35696.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email, server_id, error\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", + "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as, server_id, error\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "1d4bb4f53574ef95ef1016b760f849ec2372ac6a21bb2556d17a96dc72ea4980" + "hash": "02748cae17e8966dbd57a33017ccb747c84fcc12fbfd93c6c749570b94d35696" } diff --git a/backend/.sqlx/query-7c1ae9cac13d1387cfa94149f039054dd8c30c16b4657e73cdb0d7c7f1cb3b6d.json b/backend/.sqlx/query-02e04f9ebc0e14f98f290bf2dc3eb00bc613ba7d29f8dd5ff31a4acd0ef3adfd.json similarity index 74% rename from backend/.sqlx/query-7c1ae9cac13d1387cfa94149f039054dd8c30c16b4657e73cdb0d7c7f1cb3b6d.json rename to backend/.sqlx/query-02e04f9ebc0e14f98f290bf2dc3eb00bc613ba7d29f8dd5ff31a4acd0ef3adfd.json index cf07041291..40fd3b9683 100644 --- a/backend/.sqlx/query-7c1ae9cac13d1387cfa94149f039054dd8c30c16b4657e73cdb0d7c7f1cb3b6d.json +++ b/backend/.sqlx/query-02e04f9ebc0e14f98f290bf2dc3eb00bc613ba7d29f8dd5ff31a4acd0ef3adfd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8::trigger_mode)\n ", + "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8::trigger_mode)\n ", "describe": { "columns": [], "parameters": { @@ -28,5 +28,5 @@ }, "nullable": [] }, - "hash": "7c1ae9cac13d1387cfa94149f039054dd8c30c16b4657e73cdb0d7c7f1cb3b6d" + "hash": "02e04f9ebc0e14f98f290bf2dc3eb00bc613ba7d29f8dd5ff31a4acd0ef3adfd" } diff --git a/backend/.sqlx/query-1cad2ebfbdc46f9c0d93329897a71701f17a33b708b334d909563c9a0dcc9c23.json b/backend/.sqlx/query-066c9690d1606bf889879b7e3c686529c37db0d5f18c83706bfbc63c8c3e4315.json similarity index 67% rename from backend/.sqlx/query-1cad2ebfbdc46f9c0d93329897a71701f17a33b708b334d909563c9a0dcc9c23.json rename to backend/.sqlx/query-066c9690d1606bf889879b7e3c686529c37db0d5f18c83706bfbc63c8c3e4315.json index 49ae463657..8f4c29a819 100644 --- a/backend/.sqlx/query-1cad2ebfbdc46f9c0d93329897a71701f17a33b708b334d909563c9a0dcc9c23.json +++ b/backend/.sqlx/query-066c9690d1606bf889879b7e3c686529c37db0d5f18c83706bfbc63c8c3e4315.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", + "query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "1cad2ebfbdc46f9c0d93329897a71701f17a33b708b334d909563c9a0dcc9c23" + "hash": "066c9690d1606bf889879b7e3c686529c37db0d5f18c83706bfbc63c8c3e4315" } diff --git a/backend/.sqlx/query-0f26c74f604e1c3c613de8ba654cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e.json b/backend/.sqlx/query-0f26c74f604e1c3c613de8ba654cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e.json new file mode 100644 index 0000000000..3cb9ceabbb --- /dev/null +++ b/backend/.sqlx/query-0f26c74f604e1c3c613de8ba654cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "email", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "edited_by", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "0f26c74f604e1c3c613de8ba654cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e" +} diff --git a/backend/.sqlx/query-6afa076744233fc5e92188ff978990fa3a704afe3eec523f4e203f7f6e247261.json b/backend/.sqlx/query-13d60d85694b5a5fcfc7687a07b78a54ff53245271466b1f3a9d9edf43cdaa1f.json similarity index 71% rename from backend/.sqlx/query-6afa076744233fc5e92188ff978990fa3a704afe3eec523f4e203f7f6e247261.json rename to backend/.sqlx/query-13d60d85694b5a5fcfc7687a07b78a54ff53245271466b1f3a9d9edf43cdaa1f.json index b34e0205c9..dbdbbef779 100644 --- a/backend/.sqlx/query-6afa076744233fc5e92188ff978990fa3a704afe3eec523f4e203f7f6e247261.json +++ b/backend/.sqlx/query-13d60d85694b5a5fcfc7687a07b78a54ff53245271466b1f3a9d9edf43cdaa1f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, email, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ", + "query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, permissioned_as, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ", "describe": { "columns": [], "parameters": { @@ -50,5 +50,5 @@ }, "nullable": [] }, - "hash": "6afa076744233fc5e92188ff978990fa3a704afe3eec523f4e203f7f6e247261" + "hash": "13d60d85694b5a5fcfc7687a07b78a54ff53245271466b1f3a9d9edf43cdaa1f" } diff --git a/backend/.sqlx/query-388ff2abd495cf71e87cf0c4ddc73b6c84867fb966df91b320c54acdd5e61315.json b/backend/.sqlx/query-1b7803a2060a19cb6e71f1e97619891ea8449b4a9433908cf717738846f7eec5.json similarity index 69% rename from backend/.sqlx/query-388ff2abd495cf71e87cf0c4ddc73b6c84867fb966df91b320c54acdd5e61315.json rename to backend/.sqlx/query-1b7803a2060a19cb6e71f1e97619891ea8449b4a9433908cf717738846f7eec5.json index 3f5bd4aefb..5cdb598eba 100644 --- a/backend/.sqlx/query-388ff2abd495cf71e87cf0c4ddc73b6c84867fb966df91b320c54acdd5e61315.json +++ b/backend/.sqlx/query-1b7803a2060a19cb6e71f1e97619891ea8449b4a9433908cf717738846f7eec5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n email = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n mode = $11\n WHERE\n workspace_id = $12 AND path = $13\n ", + "query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n local_part = $4,\n workspaced_local_part = $5,\n edited_by = $6,\n permissioned_as = $7,\n edited_at = now(),\n error_handler_path = $8,\n error_handler_args = $9,\n retry = $10,\n mode = $11\n WHERE\n workspace_id = $12 AND path = $13\n ", "describe": { "columns": [], "parameters": { @@ -33,5 +33,5 @@ }, "nullable": [] }, - "hash": "388ff2abd495cf71e87cf0c4ddc73b6c84867fb966df91b320c54acdd5e61315" + "hash": "1b7803a2060a19cb6e71f1e97619891ea8449b4a9433908cf717738846f7eec5" } diff --git a/backend/.sqlx/query-c723c3a5066a487b93e2642993f3bf624a1f50d06c7de75157420d97cf144763.json b/backend/.sqlx/query-1d2514b3d75ffb6cc0eb09ceb8fde974a07881eb08164e885448d9a17c4ca6db.json similarity index 67% rename from backend/.sqlx/query-c723c3a5066a487b93e2642993f3bf624a1f50d06c7de75157420d97cf144763.json rename to backend/.sqlx/query-1d2514b3d75ffb6cc0eb09ceb8fde974a07881eb08164e885448d9a17c4ca6db.json index ef146c1cee..d47beb6e0f 100644 --- a/backend/.sqlx/query-c723c3a5066a487b93e2642993f3bf624a1f50d06c7de75157420d97cf144763.json +++ b/backend/.sqlx/query-1d2514b3d75ffb6cc0eb09ceb8fde974a07881eb08164e885448d9a17c4ca6db.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE sqs_trigger\n SET\n queue_url = $1,\n aws_resource_path = $2,\n message_attributes = $3,\n aws_auth_resource_type = $4,\n script_path = $5,\n path = $6,\n is_flow = $7,\n edited_by = $8,\n email = $9,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $12,\n error_handler_args = $13,\n retry = $14\n WHERE\n workspace_id = $10 AND path = $11\n ", + "query": "\n UPDATE sqs_trigger\n SET\n queue_url = $1,\n aws_resource_path = $2,\n message_attributes = $3,\n aws_auth_resource_type = $4,\n script_path = $5,\n path = $6,\n is_flow = $7,\n edited_by = $8,\n permissioned_as = $9,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $12,\n error_handler_args = $13,\n retry = $14\n WHERE\n workspace_id = $10 AND path = $11\n ", "describe": { "columns": [], "parameters": { @@ -33,5 +33,5 @@ }, "nullable": [] }, - "hash": "c723c3a5066a487b93e2642993f3bf624a1f50d06c7de75157420d97cf144763" + "hash": "1d2514b3d75ffb6cc0eb09ceb8fde974a07881eb08164e885448d9a17c4ca6db" } diff --git a/backend/.sqlx/query-1074c6c98e6a0c83ac04172a39abea21c793f58947051d39931d4da0868a1d77.json b/backend/.sqlx/query-1e28751bb98a1c477c0e582a2a39f81bf34e2d72f35ef1ea5d8c057ec9e694d8.json similarity index 67% rename from backend/.sqlx/query-1074c6c98e6a0c83ac04172a39abea21c793f58947051d39931d4da0868a1d77.json rename to backend/.sqlx/query-1e28751bb98a1c477c0e582a2a39f81bf34e2d72f35ef1ea5d8c057ec9e694d8.json index 74535ae3c7..99d7695e8c 100644 --- a/backend/.sqlx/query-1074c6c98e6a0c83ac04172a39abea21c793f58947051d39931d4da0868a1d77.json +++ b/backend/.sqlx/query-1e28751bb98a1c477c0e582a2a39f81bf34e2d72f35ef1ea5d8c057ec9e694d8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO email_trigger (\n path, local_part, workspaced_local_part, script_path,\n is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", + "query": "\n INSERT INTO email_trigger (\n path, local_part, workspaced_local_part, script_path,\n is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", "describe": { "columns": [], "parameters": { @@ -17,5 +17,5 @@ }, "nullable": [] }, - "hash": "1074c6c98e6a0c83ac04172a39abea21c793f58947051d39931d4da0868a1d77" + "hash": "1e28751bb98a1c477c0e582a2a39f81bf34e2d72f35ef1ea5d8c057ec9e694d8" } diff --git a/backend/.sqlx/query-45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf.json b/backend/.sqlx/query-1ef63255389bdc47d5392a84aad38adf1ccc3a4923f988d8abaafc9749307c0e.json similarity index 66% rename from backend/.sqlx/query-45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf.json rename to backend/.sqlx/query-1ef63255389bdc47d5392a84aad38adf1ccc3a4923f988d8abaafc9749307c0e.json index b5873760fc..d175e5374b 100644 --- a/backend/.sqlx/query-45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf.json +++ b/backend/.sqlx/query-1ef63255389bdc47d5392a84aad38adf1ccc3a4923f988d8abaafc9749307c0e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, email, auto_commit\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ", + "query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id, script_path,\n is_flow, workspace_id, edited_by, permissioned_as, auto_commit\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ", "describe": { "columns": [], "parameters": { @@ -19,5 +19,5 @@ }, "nullable": [] }, - "hash": "45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf" + "hash": "1ef63255389bdc47d5392a84aad38adf1ccc3a4923f988d8abaafc9749307c0e" } diff --git a/backend/.sqlx/query-e486a64b76da5de97e404c81dd6e29d333ada2dcfbbddb028f37794b85778ca8.json b/backend/.sqlx/query-1f693e2fba9885f7fc49bd2994240c4421fdae4aab496a3397c5c07d960582f2.json similarity index 63% rename from backend/.sqlx/query-e486a64b76da5de97e404c81dd6e29d333ada2dcfbbddb028f37794b85778ca8.json rename to backend/.sqlx/query-1f693e2fba9885f7fc49bd2994240c4421fdae4aab496a3397c5c07d960582f2.json index a9d57f3132..13c7b09b37 100644 --- a/backend/.sqlx/query-e486a64b76da5de97e404c81dd6e29d333ada2dcfbbddb028f37794b85778ca8.json +++ b/backend/.sqlx/query-1f693e2fba9885f7fc49bd2994240c4421fdae4aab496a3397c5c07d960582f2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE\n mqtt_trigger\n SET\n mqtt_resource_path = $1,\n subscribe_topics = $2,\n client_version = $3,\n client_id = $4,\n v3_config = $5,\n v5_config = $6,\n is_flow = $7,\n edited_by = $8,\n email = $9,\n script_path = $10,\n path = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND\n path = $13\n ", + "query": "\n UPDATE\n mqtt_trigger\n SET\n mqtt_resource_path = $1,\n subscribe_topics = $2,\n client_version = $3,\n client_id = $4,\n v3_config = $5,\n v5_config = $6,\n is_flow = $7,\n edited_by = $8,\n permissioned_as = $9,\n script_path = $10,\n path = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16\n WHERE\n workspace_id = $12 AND\n path = $13\n ", "describe": { "columns": [], "parameters": { @@ -35,5 +35,5 @@ }, "nullable": [] }, - "hash": "e486a64b76da5de97e404c81dd6e29d333ada2dcfbbddb028f37794b85778ca8" + "hash": "1f693e2fba9885f7fc49bd2994240c4421fdae4aab496a3397c5c07d960582f2" } diff --git a/backend/.sqlx/query-fb942aa7894b4ae904f0233405f62f201e3f5deed593128017b886342ef6d210.json b/backend/.sqlx/query-21d7ce033b5f67499f579aeae98806599401fcfa9765a58b8cd45a20b411d0ca.json similarity index 70% rename from backend/.sqlx/query-fb942aa7894b4ae904f0233405f62f201e3f5deed593128017b886342ef6d210.json rename to backend/.sqlx/query-21d7ce033b5f67499f579aeae98806599401fcfa9765a58b8cd45a20b411d0ca.json index 942c881b38..f401e110f2 100644 --- a/backend/.sqlx/query-fb942aa7894b4ae904f0233405f62f201e3f5deed593128017b886342ef6d210.json +++ b/backend/.sqlx/query-21d7ce033b5f67499f579aeae98806599401fcfa9765a58b8cd45a20b411d0ca.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO postgres_trigger (\n workspace_id,\n path,\n postgres_resource_path,\n replication_slot_name,\n publication_name,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\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, now(), $11, $12, $13\n )\n ", + "query": "\n INSERT INTO postgres_trigger (\n workspace_id,\n path,\n postgres_resource_path,\n replication_slot_name,\n publication_name,\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, now(), $11, $12, $13\n )\n ", "describe": { "columns": [], "parameters": { @@ -33,5 +33,5 @@ }, "nullable": [] }, - "hash": "fb942aa7894b4ae904f0233405f62f201e3f5deed593128017b886342ef6d210" + "hash": "21d7ce033b5f67499f579aeae98806599401fcfa9765a58b8cd45a20b411d0ca" } diff --git a/backend/.sqlx/query-270cfaea4f888e73e21a957e0328ec1f990fce409160eb7b0e807bff56defff4.json b/backend/.sqlx/query-270cfaea4f888e73e21a957e0328ec1f990fce409160eb7b0e807bff56defff4.json new file mode 100644 index 0000000000..e5b7acb079 --- /dev/null +++ b/backend/.sqlx/query-270cfaea4f888e73e21a957e0328ec1f990fce409160eb7b0e807bff56defff4.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT created_by, permissioned_as, permissioned_as_email\n FROM v2_job\n WHERE workspace_id = 'test-workspace'\n AND trigger_kind = 'schedule'\n AND trigger = $1\n ORDER BY created_at DESC\n LIMIT 1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "created_by", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "permissioned_as_email", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "270cfaea4f888e73e21a957e0328ec1f990fce409160eb7b0e807bff56defff4" +} diff --git a/backend/.sqlx/query-8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854.json b/backend/.sqlx/query-39062cdb183b97906c25602000321a76d4e629ba027364190a2487cc9bd93235.json similarity index 62% rename from backend/.sqlx/query-8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854.json rename to backend/.sqlx/query-39062cdb183b97906c25602000321a76d4e629ba027364190a2487cc9bd93235.json index 4d2ad74bf6..43b0f69756 100644 --- a/backend/.sqlx/query-8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854.json +++ b/backend/.sqlx/query-39062cdb183b97906c25602000321a76d4e629ba027364190a2487cc9bd93235.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", + "query": "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "email", + "name": "permissioned_as", "type_info": "Varchar" }, { @@ -25,5 +25,5 @@ false ] }, - "hash": "8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854" + "hash": "39062cdb183b97906c25602000321a76d4e629ba027364190a2487cc9bd93235" } diff --git a/backend/.sqlx/query-3d763dbb411e28ce026cc9ab525b20b409cfe17bf1cdef22aaffc719cf6c53e3.json b/backend/.sqlx/query-3aa3d0362fa8ed97dd034454b05f14880c9bb43d0c3ce4ea9b4511aa2d092d0c.json similarity index 68% rename from backend/.sqlx/query-3d763dbb411e28ce026cc9ab525b20b409cfe17bf1cdef22aaffc719cf6c53e3.json rename to backend/.sqlx/query-3aa3d0362fa8ed97dd034454b05f14880c9bb43d0c3ce4ea9b4511aa2d092d0c.json index 72806fdb1e..2622aa1083 100644 --- a/backend/.sqlx/query-3d763dbb411e28ce026cc9ab525b20b409cfe17bf1cdef22aaffc719cf6c53e3.json +++ b/backend/.sqlx/query-3aa3d0362fa8ed97dd034454b05f14880c9bb43d0c3ce4ea9b4511aa2d092d0c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n email,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n mode\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ", + "query": "\n INSERT INTO email_trigger (\n workspace_id,\n path,\n script_path,\n is_flow,\n local_part,\n workspaced_local_part,\n edited_by,\n permissioned_as,\n edited_at,\n error_handler_path,\n error_handler_args,\n retry,\n mode\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, now(), $9, $10, $11, $12\n )\n ", "describe": { "columns": [], "parameters": { @@ -32,5 +32,5 @@ }, "nullable": [] }, - "hash": "3d763dbb411e28ce026cc9ab525b20b409cfe17bf1cdef22aaffc719cf6c53e3" + "hash": "3aa3d0362fa8ed97dd034454b05f14880c9bb43d0c3ce4ea9b4511aa2d092d0c" } diff --git a/backend/.sqlx/query-ee9ee0fbf5dd72d190e18c56622244b984fdb36fbf70197d9f8cb6306c9670db.json b/backend/.sqlx/query-41ad9954bebe31b0545837147b1616da2e6743f3a45775cd556a7414bc8f9726.json similarity index 81% rename from backend/.sqlx/query-ee9ee0fbf5dd72d190e18c56622244b984fdb36fbf70197d9f8cb6306c9670db.json rename to backend/.sqlx/query-41ad9954bebe31b0545837147b1616da2e6743f3a45775cd556a7414bc8f9726.json index 47dafd738c..61c80db067 100644 --- a/backend/.sqlx/query-ee9ee0fbf5dd72d190e18c56622244b984fdb36fbf70197d9f8cb6306c9670db.json +++ b/backend/.sqlx/query-41ad9954bebe31b0545837147b1616da2e6743f3a45775cd556a7414bc8f9726.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n mode,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n email,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ", + "query": "\n INSERT INTO http_trigger (\n workspace_id,\n path,\n route_path,\n route_path_key,\n workspaced_route,\n authentication_resource_path,\n wrap_body,\n raw_string,\n script_path,\n summary,\n description,\n is_flow,\n mode,\n request_type,\n authentication_method,\n http_method,\n static_asset_config,\n edited_by,\n permissioned_as,\n edited_at,\n is_static_website,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, now(), $20, $21, $22, $23\n )\n ", "describe": { "columns": [], "parameters": { @@ -81,5 +81,5 @@ }, "nullable": [] }, - "hash": "ee9ee0fbf5dd72d190e18c56622244b984fdb36fbf70197d9f8cb6306c9670db" + "hash": "41ad9954bebe31b0545837147b1616da2e6743f3a45775cd556a7414bc8f9726" } diff --git a/backend/.sqlx/query-9f41ea5cbe4cffa74e4a283fe8f023c813e349956487f7b6599da452c068e9b9.json b/backend/.sqlx/query-4ba114f54ed88c27dd4aed05a273c91e98913397b310ff760c1dbb9077764e9b.json similarity index 66% rename from backend/.sqlx/query-9f41ea5cbe4cffa74e4a283fe8f023c813e349956487f7b6599da452c068e9b9.json rename to backend/.sqlx/query-4ba114f54ed88c27dd4aed05a273c91e98913397b310ff760c1dbb9077764e9b.json index 776ff7b5e3..313de9f23d 100644 --- a/backend/.sqlx/query-9f41ea5cbe4cffa74e4a283fe8f023c813e349956487f7b6599da452c068e9b9.json +++ b/backend/.sqlx/query-4ba114f54ed88c27dd4aed05a273c91e98913397b310ff760c1dbb9077764e9b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE nats_trigger\n SET\n nats_resource_path = $1,\n subjects = $2,\n stream_name = $3,\n consumer_name = $4,\n use_jetstream = $5,\n script_path = $6,\n path = $7,\n is_flow = $8,\n edited_by = $9,\n email = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ", + "query": "\n UPDATE nats_trigger\n SET\n nats_resource_path = $1,\n subjects = $2,\n stream_name = $3,\n consumer_name = $4,\n use_jetstream = $5,\n script_path = $6,\n path = $7,\n is_flow = $8,\n edited_by = $9,\n permissioned_as = $10,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $13,\n error_handler_args = $14,\n retry = $15\n WHERE\n workspace_id = $11 AND path = $12\n ", "describe": { "columns": [], "parameters": { @@ -24,5 +24,5 @@ }, "nullable": [] }, - "hash": "9f41ea5cbe4cffa74e4a283fe8f023c813e349956487f7b6599da452c068e9b9" + "hash": "4ba114f54ed88c27dd4aed05a273c91e98913397b310ff760c1dbb9077764e9b" } diff --git a/backend/.sqlx/query-987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0.json b/backend/.sqlx/query-54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8.json similarity index 79% rename from backend/.sqlx/query-987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0.json rename to backend/.sqlx/query-54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8.json index a2678738b6..af8c1d983e 100644 --- a/backend/.sqlx/query-987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0.json +++ b/backend/.sqlx/query-54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = COALESCE($24, email),\n edited_by = $25\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ", + "query": "\n UPDATE schedule SET\n schedule = $1,\n timezone = $2,\n args = $3,\n on_failure = $4,\n on_failure_times = $5,\n on_failure_exact = $6,\n on_failure_extra_args = $7,\n on_recovery = $8,\n on_recovery_times = $9,\n on_recovery_extra_args = $10,\n on_success = $11,\n on_success_extra_args = $12,\n ws_error_handler_muted = $13,\n retry = $14,\n summary = $15,\n no_flow_overlap = $16,\n tag = $17,\n paused_until = $18,\n path = $19,\n workspace_id = $20,\n cron_version = COALESCE($21, cron_version),\n description = $22,\n dynamic_skip = $23,\n email = $24,\n edited_by = $25,\n permissioned_as = $26\n WHERE path = $19 AND workspace_id = $20\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ", "describe": { "columns": [ { @@ -65,96 +65,101 @@ }, { "ordinal": 12, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 13, "name": "error", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 14, "name": "on_failure", "type_info": "Varchar" }, { - "ordinal": 14, + "ordinal": 15, "name": "on_failure_times", "type_info": "Int4" }, { - "ordinal": 15, + "ordinal": 16, "name": "on_failure_exact", "type_info": "Bool" }, { - "ordinal": 16, + "ordinal": 17, "name": "on_failure_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 17, + "ordinal": 18, "name": "on_recovery", "type_info": "Varchar" }, { - "ordinal": 18, + "ordinal": 19, "name": "on_recovery_times", "type_info": "Int4" }, { - "ordinal": 19, + "ordinal": 20, "name": "on_recovery_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 20, + "ordinal": 21, "name": "on_success", "type_info": "Varchar" }, { - "ordinal": 21, + "ordinal": 22, "name": "on_success_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 22, + "ordinal": 23, "name": "ws_error_handler_muted", "type_info": "Bool" }, { - "ordinal": 23, + "ordinal": 24, "name": "retry", "type_info": "Jsonb" }, { - "ordinal": 24, + "ordinal": 25, "name": "no_flow_overlap", "type_info": "Bool" }, { - "ordinal": 25, + "ordinal": 26, "name": "summary", "type_info": "Varchar" }, { - "ordinal": 26, + "ordinal": 27, "name": "description", "type_info": "Text" }, { - "ordinal": 27, + "ordinal": 28, "name": "tag", "type_info": "Varchar" }, { - "ordinal": 28, + "ordinal": 29, "name": "paused_until", "type_info": "Timestamptz" }, { - "ordinal": 29, + "ordinal": 30, "name": "cron_version", "type_info": "Text" }, { - "ordinal": 30, + "ordinal": 31, "name": "dynamic_skip", "type_info": "Varchar" } @@ -185,6 +190,7 @@ "Text", "Varchar", "Varchar", + "Varchar", "Varchar" ] }, @@ -201,6 +207,7 @@ true, false, false, + false, true, true, true, @@ -222,5 +229,5 @@ true ] }, - "hash": "987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0" + "hash": "54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8" } diff --git a/backend/.sqlx/query-2fd0d3224382b000028d98b0af4c431d3cadd54cca65d83c1ab7f2d2972e2282.json b/backend/.sqlx/query-5efbf92ac7347e73769c66ffdc4037c7e56a5939d9ffcbee13e0264cbb2a6dfe.json similarity index 77% rename from backend/.sqlx/query-2fd0d3224382b000028d98b0af4c431d3cadd54cca65d83c1ab7f2d2972e2282.json rename to backend/.sqlx/query-5efbf92ac7347e73769c66ffdc4037c7e56a5939d9ffcbee13e0264cbb2a6dfe.json index 3f53a7e6c4..473c92c016 100644 --- a/backend/.sqlx/query-2fd0d3224382b000028d98b0af4c431d3cadd54cca65d83c1ab7f2d2972e2282.json +++ b/backend/.sqlx/query-5efbf92ac7347e73769c66ffdc4037c7e56a5939d9ffcbee13e0264cbb2a6dfe.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n mode = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n email = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ", + "query": "\n UPDATE\n http_trigger\n SET\n route_path = $1,\n route_path_key = $2,\n workspaced_route = $3,\n wrap_body = $4,\n raw_string = $5,\n authentication_resource_path = $6,\n script_path = $7,\n path = $8,\n is_flow = $9,\n mode = $10,\n http_method = $11,\n static_asset_config = $12,\n edited_by = $13,\n permissioned_as = $14,\n request_type = $15,\n authentication_method = $16,\n summary = $17,\n description = $18,\n edited_at = now(),\n is_static_website = $19,\n error_handler_path = $20,\n error_handler_args = $21,\n retry = $22\n WHERE\n workspace_id = $23 AND\n path = $24\n ", "describe": { "columns": [], "parameters": { @@ -82,5 +82,5 @@ }, "nullable": [] }, - "hash": "2fd0d3224382b000028d98b0af4c431d3cadd54cca65d83c1ab7f2d2972e2282" + "hash": "5efbf92ac7347e73769c66ffdc4037c7e56a5939d9ffcbee13e0264cbb2a6dfe" } diff --git a/backend/.sqlx/query-1cf2eb1426e8be89c3649272103bcd029e99b029b7f1b71eda4411d1e24e790d.json b/backend/.sqlx/query-6cfa6b5f16207863a03b77fffbb94ae91872a207ea2362e49e100ed1f0b35198.json similarity index 78% rename from backend/.sqlx/query-1cf2eb1426e8be89c3649272103bcd029e99b029b7f1b71eda4411d1e24e790d.json rename to backend/.sqlx/query-6cfa6b5f16207863a03b77fffbb94ae91872a207ea2362e49e100ed1f0b35198.json index 851c98634b..ee4e3eaf7a 100644 --- a/backend/.sqlx/query-1cf2eb1426e8be89c3649272103bcd029e99b029b7f1b71eda4411d1e24e790d.json +++ b/backend/.sqlx/query-6cfa6b5f16207863a03b77fffbb94ae91872a207ea2362e49e100ed1f0b35198.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n gcp_resource_path,\n script_path,\n is_flow,\n mode as \"mode: _\",\n workspace_id,\n path,\n edited_by,\n email,\n delivery_config AS \"delivery_config: _\",\n retry as \"retry: _\",\n error_handler_path,\n error_handler_args as \"error_handler_args: _\"\n FROM\n gcp_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n delivery_type = 'push'::DELIVERY_MODE\n ", + "query": "\n SELECT\n gcp_resource_path,\n script_path,\n is_flow,\n mode as \"mode: _\",\n workspace_id,\n path,\n edited_by,\n permissioned_as,\n delivery_config AS \"delivery_config: _\",\n retry as \"retry: _\",\n error_handler_path,\n error_handler_args as \"error_handler_args: _\"\n FROM\n gcp_trigger\n WHERE\n workspace_id = $1 AND\n path = $2 AND\n delivery_type = 'push'::DELIVERY_MODE\n ", "describe": { "columns": [ { @@ -51,7 +51,7 @@ }, { "ordinal": 7, - "name": "email", + "name": "permissioned_as", "type_info": "Varchar" }, { @@ -96,5 +96,5 @@ true ] }, - "hash": "1cf2eb1426e8be89c3649272103bcd029e99b029b7f1b71eda4411d1e24e790d" + "hash": "6cfa6b5f16207863a03b77fffbb94ae91872a207ea2362e49e100ed1f0b35198" } diff --git a/backend/.sqlx/query-57b7236cae0b6a1940f4c2d4b202692450ee231488d9a55ca59ff53a6f674626.json b/backend/.sqlx/query-72bc0b4acb3fb155436df74b0bb11600df8e55d50fa48d21ce2c5ae82eaf9f1c.json similarity index 69% rename from backend/.sqlx/query-57b7236cae0b6a1940f4c2d4b202692450ee231488d9a55ca59ff53a6f674626.json rename to backend/.sqlx/query-72bc0b4acb3fb155436df74b0bb11600df8e55d50fa48d21ce2c5ae82eaf9f1c.json index 0b854e8bef..be0bf70eab 100644 --- a/backend/.sqlx/query-57b7236cae0b6a1940f4c2d4b202692450ee231488d9a55ca59ff53a6f674626.json +++ b/backend/.sqlx/query-72bc0b4acb3fb155436df74b0bb11600df8e55d50fa48d21ce2c5ae82eaf9f1c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ", + "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ", "describe": { "columns": [], "parameters": { @@ -16,5 +16,5 @@ }, "nullable": [] }, - "hash": "57b7236cae0b6a1940f4c2d4b202692450ee231488d9a55ca59ff53a6f674626" + "hash": "72bc0b4acb3fb155436df74b0bb11600df8e55d50fa48d21ce2c5ae82eaf9f1c" } diff --git a/backend/.sqlx/query-3cd37daa80bc3697d331c19e01a49916fc03fdf9eceff73fa153020b4a48f4a2.json b/backend/.sqlx/query-7402639802ba5f286db8436d21dcce24aa615fe1db7e8b4fc468963216aab69d.json similarity index 81% rename from backend/.sqlx/query-3cd37daa80bc3697d331c19e01a49916fc03fdf9eceff73fa153020b4a48f4a2.json rename to backend/.sqlx/query-7402639802ba5f286db8436d21dcce24aa615fe1db7e8b4fc468963216aab69d.json index 2159a97360..dd0be2191c 100644 --- a/backend/.sqlx/query-3cd37daa80bc3697d331c19e01a49916fc03fdf9eceff73fa153020b4a48f4a2.json +++ b/backend/.sqlx/query-7402639802ba5f286db8436d21dcce24aa615fe1db7e8b4fc468963216aab69d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n email,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n mode as \"mode: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ", + "query": "\n SELECT\n path,\n script_path,\n is_flow,\n route_path,\n authentication_resource_path,\n workspace_id,\n request_type AS \"request_type: _\",\n authentication_method AS \"authentication_method: _\",\n edited_by,\n permissioned_as,\n static_asset_config AS \"static_asset_config: _\",\n wrap_body,\n raw_string,\n workspaced_route,\n is_static_website,\n error_handler_path,\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\",\n mode as \"mode: _\"\n FROM\n http_trigger\n WHERE\n http_method = $1 AND\n (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ", "describe": { "columns": [ { @@ -75,7 +75,7 @@ }, { "ordinal": 9, - "name": "email", + "name": "permissioned_as", "type_info": "Varchar" }, { @@ -175,5 +175,5 @@ false ] }, - "hash": "3cd37daa80bc3697d331c19e01a49916fc03fdf9eceff73fa153020b4a48f4a2" + "hash": "7402639802ba5f286db8436d21dcce24aa615fe1db7e8b4fc468963216aab69d" } diff --git a/backend/.sqlx/query-75b9a2153ba12ef443212d83743605e0f9d8c1f0fbe3271f0e65b1dc5cd47987.json b/backend/.sqlx/query-75b9a2153ba12ef443212d83743605e0f9d8c1f0fbe3271f0e65b1dc5cd47987.json new file mode 100644 index 0000000000..3bb6d1a671 --- /dev/null +++ b/backend/.sqlx/query-75b9a2153ba12ef443212d83743605e0f9d8c1f0fbe3271f0e65b1dc5cd47987.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO nats_trigger (path, nats_resource_path, subjects, use_jetstream, script_path, is_flow, workspace_id, edited_by, permissioned_as) VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "VarcharArray", + "Bool", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "75b9a2153ba12ef443212d83743605e0f9d8c1f0fbe3271f0e65b1dc5cd47987" +} diff --git a/backend/.sqlx/query-17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c.json b/backend/.sqlx/query-893ff34f2b22cf89a24a0b613ed390077fe6c75f56a3419f530e542bab0fb1a4.json similarity index 63% rename from backend/.sqlx/query-17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c.json rename to backend/.sqlx/query-893ff34f2b22cf89a24a0b613ed390077fe6c75f56a3419f530e542bab0fb1a4.json index babc193586..de5f684e08 100644 --- a/backend/.sqlx/query-17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c.json +++ b/backend/.sqlx/query-893ff34f2b22cf89a24a0b613ed390077fe6c75f56a3419f530e542bab0fb1a4.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "query": "SELECT permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "email", + "name": "permissioned_as", "type_info": "Varchar" }, { @@ -25,5 +25,5 @@ false ] }, - "hash": "17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c" + "hash": "893ff34f2b22cf89a24a0b613ed390077fe6c75f56a3419f530e542bab0fb1a4" } diff --git a/backend/.sqlx/query-57eca702e951f5303a74643c7ba64472e2c2a781fbb6366d998a0f1ca22fcdf2.json b/backend/.sqlx/query-8d53b0f2df5fdb6c43b9f5e92c8a97669676249dbf512ff39db94ecacc51e04e.json similarity index 74% rename from backend/.sqlx/query-57eca702e951f5303a74643c7ba64472e2c2a781fbb6366d998a0f1ca22fcdf2.json rename to backend/.sqlx/query-8d53b0f2df5fdb6c43b9f5e92c8a97669676249dbf512ff39db94ecacc51e04e.json index e55868665f..d9279184b4 100644 --- a/backend/.sqlx/query-57eca702e951f5303a74643c7ba64472e2c2a781fbb6366d998a0f1ca22fcdf2.json +++ b/backend/.sqlx/query-8d53b0f2df5fdb6c43b9f5e92c8a97669676249dbf512ff39db94ecacc51e04e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, email, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14, $15::trigger_mode)\n ", + "query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, permissioned_as, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string, mode\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14, $15::trigger_mode)\n ", "describe": { "columns": [], "parameters": { @@ -62,5 +62,5 @@ }, "nullable": [] }, - "hash": "57eca702e951f5303a74643c7ba64472e2c2a781fbb6366d998a0f1ca22fcdf2" + "hash": "8d53b0f2df5fdb6c43b9f5e92c8a97669676249dbf512ff39db94ecacc51e04e" } diff --git a/backend/.sqlx/query-75ce9753a8acc8eccb3f2e0baaa5a871f866a8d21961fac8a003bc40c406ee79.json b/backend/.sqlx/query-906773b0de209734e9cf9c6421d4b3bf95ed2e00ac07942d8bba2b11b8462d3b.json similarity index 65% rename from backend/.sqlx/query-75ce9753a8acc8eccb3f2e0baaa5a871f866a8d21961fac8a003bc40c406ee79.json rename to backend/.sqlx/query-906773b0de209734e9cf9c6421d4b3bf95ed2e00ac07942d8bba2b11b8462d3b.json index 64d95155d1..d9548d0256 100644 --- a/backend/.sqlx/query-75ce9753a8acc8eccb3f2e0baaa5a871f866a8d21961fac8a003bc40c406ee79.json +++ b/backend/.sqlx/query-906773b0de209734e9cf9c6421d4b3bf95ed2e00ac07942d8bba2b11b8462d3b.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO schedule (\n workspace_id, path, edited_by, schedule, enabled,\n script_path, is_flow, email, timezone\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", + "query": "\n INSERT INTO schedule (\n workspace_id, path, edited_by, schedule, enabled,\n script_path, is_flow, email, timezone, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)\n ", "describe": { "columns": [], "parameters": { @@ -13,10 +13,11 @@ "Varchar", "Bool", "Varchar", + "Varchar", "Varchar" ] }, "nullable": [] }, - "hash": "75ce9753a8acc8eccb3f2e0baaa5a871f866a8d21961fac8a003bc40c406ee79" + "hash": "906773b0de209734e9cf9c6421d4b3bf95ed2e00ac07942d8bba2b11b8462d3b" } diff --git a/backend/.sqlx/query-e99c958628d83e3fea054eaa182b1301e0b04c7eb9801d9de13b37bd908d902f.json b/backend/.sqlx/query-942c0abb55c910862fd45d3fa56a4eb6729f1a658101bda2d0b0fca96b3cfee5.json similarity index 71% rename from backend/.sqlx/query-e99c958628d83e3fea054eaa182b1301e0b04c7eb9801d9de13b37bd908d902f.json rename to backend/.sqlx/query-942c0abb55c910862fd45d3fa56a4eb6729f1a658101bda2d0b0fca96b3cfee5.json index 8993a7c789..e3ca43fbd0 100644 --- a/backend/.sqlx/query-e99c958628d83e3fea054eaa182b1301e0b04c7eb9801d9de13b37bd908d902f.json +++ b/backend/.sqlx/query-942c0abb55c910862fd45d3fa56a4eb6729f1a658101bda2d0b0fca96b3cfee5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO websocket_trigger (\n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n mode,\n filters,\n initial_messages,\n url_runnable_args,\n edited_by,\n can_return_message,\n can_return_error_result,\n email,\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 ", + "query": "\n INSERT INTO websocket_trigger (\n workspace_id,\n path,\n url,\n script_path,\n is_flow,\n mode,\n filters,\n initial_messages,\n url_runnable_args,\n edited_by,\n can_return_message,\n can_return_error_result,\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": { @@ -36,5 +36,5 @@ }, "nullable": [] }, - "hash": "e99c958628d83e3fea054eaa182b1301e0b04c7eb9801d9de13b37bd908d902f" + "hash": "942c0abb55c910862fd45d3fa56a4eb6729f1a658101bda2d0b0fca96b3cfee5" } diff --git a/backend/.sqlx/query-97c60a4193a75b611db41543e7fe6da81fc631cc6ac43576f8a18afedad4d4a4.json b/backend/.sqlx/query-9883cff4f988767aeea7bce8b66672029b2a4c90e06c05d4356b3ec6bd8d2748.json similarity index 67% rename from backend/.sqlx/query-97c60a4193a75b611db41543e7fe6da81fc631cc6ac43576f8a18afedad4d4a4.json rename to backend/.sqlx/query-9883cff4f988767aeea7bce8b66672029b2a4c90e06c05d4356b3ec6bd8d2748.json index b5c19d2017..b99b7112f0 100644 --- a/backend/.sqlx/query-97c60a4193a75b611db41543e7fe6da81fc631cc6ac43576f8a18afedad4d4a4.json +++ b/backend/.sqlx/query-9883cff4f988767aeea7bce8b66672029b2a4c90e06c05d4356b3ec6bd8d2748.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO nats_trigger (\n path, nats_resource_path, subjects, script_path,\n is_flow, workspace_id, edited_by, email, use_jetstream\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", + "query": "\n INSERT INTO nats_trigger (\n path, nats_resource_path, subjects, script_path,\n is_flow, workspace_id, edited_by, permissioned_as, use_jetstream\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "97c60a4193a75b611db41543e7fe6da81fc631cc6ac43576f8a18afedad4d4a4" + "hash": "9883cff4f988767aeea7bce8b66672029b2a4c90e06c05d4356b3ec6bd8d2748" } diff --git a/backend/.sqlx/query-5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f.json b/backend/.sqlx/query-a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2.json similarity index 72% rename from backend/.sqlx/query-5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f.json rename to backend/.sqlx/query-a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2.json index 936a4650f6..99d8d2a181 100644 --- a/backend/.sqlx/query-5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f.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 auto_offset_reset,\n auto_commit,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\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 ", + "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": { @@ -36,5 +36,5 @@ }, "nullable": [] }, - "hash": "5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f" + "hash": "a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2" } diff --git a/backend/.sqlx/query-072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04.json b/backend/.sqlx/query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json similarity index 65% rename from backend/.sqlx/query-072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04.json rename to backend/.sqlx/query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json index 812c323e74..c993120dae 100644 --- a/backend/.sqlx/query-072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04.json +++ b/backend/.sqlx/query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json @@ -1,6 +1,6 @@ { "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 email = $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 ", + "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": { @@ -25,5 +25,5 @@ }, "nullable": [] }, - "hash": "072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04" + "hash": "a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7" } diff --git a/backend/.sqlx/query-aa59a96bf2d7edfa7c550e66c4d52ddc8e84eacc633e361e49a5219d0bec94b9.json b/backend/.sqlx/query-a5b6ca174b7a3be1b6507ef40b9cc67d85089199c900afc6668f2336b803562e.json similarity index 63% rename from backend/.sqlx/query-aa59a96bf2d7edfa7c550e66c4d52ddc8e84eacc633e361e49a5219d0bec94b9.json rename to backend/.sqlx/query-a5b6ca174b7a3be1b6507ef40b9cc67d85089199c900afc6668f2336b803562e.json index 24efa05c77..3ff75bd002 100644 --- a/backend/.sqlx/query-aa59a96bf2d7edfa7c550e66c4d52ddc8e84eacc633e361e49a5219d0bec94b9.json +++ b/backend/.sqlx/query-a5b6ca174b7a3be1b6507ef40b9cc67d85089199c900afc6668f2336b803562e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n email,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry,\n auto_acknowledge_msg,\n ack_deadline\n )\n VALUES (\n $1,\n $2,\n $3,\n $4,\n $5,\n $6,\n $7,\n $8,\n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16,\n $17\n )", + "query": "\n INSERT INTO gcp_trigger (\n gcp_resource_path,\n subscription_id,\n topic_id,\n delivery_type,\n delivery_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n permissioned_as,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry,\n auto_acknowledge_msg,\n ack_deadline\n )\n VALUES (\n $1,\n $2,\n $3,\n $4,\n $5,\n $6,\n $7,\n $8,\n $9,\n $10,\n $11,\n $12,\n $13,\n $14,\n $15,\n $16,\n $17\n )", "describe": { "columns": [], "parameters": { @@ -47,5 +47,5 @@ }, "nullable": [] }, - "hash": "aa59a96bf2d7edfa7c550e66c4d52ddc8e84eacc633e361e49a5219d0bec94b9" + "hash": "a5b6ca174b7a3be1b6507ef40b9cc67d85089199c900afc6668f2336b803562e" } diff --git a/backend/.sqlx/query-0300afc35a880eef163dfdfd9d5299fac14562ee8595c792f3c30d042fa2d3eb.json b/backend/.sqlx/query-a8245a3b29927c26894be884c38e4d848674d5318e20bb5fc6c4261da25744ca.json similarity index 55% rename from backend/.sqlx/query-0300afc35a880eef163dfdfd9d5299fac14562ee8595c792f3c30d042fa2d3eb.json rename to backend/.sqlx/query-a8245a3b29927c26894be884c38e4d848674d5318e20bb5fc6c4261da25744ca.json index cf0856b775..b853296aaa 100644 --- a/backend/.sqlx/query-0300afc35a880eef163dfdfd9d5299fac14562ee8595c792f3c30d042fa2d3eb.json +++ b/backend/.sqlx/query-a8245a3b29927c26894be884c38e4d848674d5318e20bb5fc6c4261da25744ca.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO postgres_trigger (\n path, script_path, is_flow, workspace_id, edited_by, email,\n postgres_resource_path, replication_slot_name, publication_name\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", + "query": "\n INSERT INTO postgres_trigger (\n path, script_path, is_flow, workspace_id, edited_by, permissioned_as,\n postgres_resource_path, replication_slot_name, publication_name\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "0300afc35a880eef163dfdfd9d5299fac14562ee8595c792f3c30d042fa2d3eb" + "hash": "a8245a3b29927c26894be884c38e4d848674d5318e20bb5fc6c4261da25744ca" } diff --git a/backend/.sqlx/query-b5cda8eb32384f315689001f45676d4bf44cb4397dd0a722e7c9d035b58a09c1.json b/backend/.sqlx/query-aa7d3d159943250787a8ebc964cb46e5fbb157d1ec0684789a8d37eff1adf9a5.json similarity index 63% rename from backend/.sqlx/query-b5cda8eb32384f315689001f45676d4bf44cb4397dd0a722e7c9d035b58a09c1.json rename to backend/.sqlx/query-aa7d3d159943250787a8ebc964cb46e5fbb157d1ec0684789a8d37eff1adf9a5.json index e30f73a14c..6e27a7f01f 100644 --- a/backend/.sqlx/query-b5cda8eb32384f315689001f45676d4bf44cb4397dd0a722e7c9d035b58a09c1.json +++ b/backend/.sqlx/query-aa7d3d159943250787a8ebc964cb46e5fbb157d1ec0684789a8d37eff1adf9a5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n email = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n mode = $9\n WHERE\n workspace_id = $10 AND path = $11\n ", + "query": "\n UPDATE email_trigger\n SET\n script_path = $1,\n path = $2,\n is_flow = $3,\n edited_by = $4,\n permissioned_as = $5,\n edited_at = now(),\n error_handler_path = $6,\n error_handler_args = $7,\n retry = $8,\n mode = $9\n WHERE\n workspace_id = $10 AND path = $11\n ", "describe": { "columns": [], "parameters": { @@ -31,5 +31,5 @@ }, "nullable": [] }, - "hash": "b5cda8eb32384f315689001f45676d4bf44cb4397dd0a722e7c9d035b58a09c1" + "hash": "aa7d3d159943250787a8ebc964cb46e5fbb157d1ec0684789a8d37eff1adf9a5" } diff --git a/backend/.sqlx/query-7927b80ce75d99b2a30f6b29196af000578a3c166509f032d14452cc637d884f.json b/backend/.sqlx/query-ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91.json similarity index 82% rename from backend/.sqlx/query-7927b80ce75d99b2a30f6b29196af000578a3c166509f032d14452cc637d884f.json rename to backend/.sqlx/query-ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91.json index 84585a66a9..43f77fe77c 100644 --- a/backend/.sqlx/query-7927b80ce75d99b2a30f6b29196af000578a3c166509f032d14452cc637d884f.json +++ b/backend/.sqlx/query-ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ", + "query": "\n UPDATE schedule SET\n enabled = $1,\n email = $2\n WHERE path = $3 AND workspace_id = $4\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ", "describe": { "columns": [ { @@ -65,96 +65,101 @@ }, { "ordinal": 12, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 13, "name": "error", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 14, "name": "on_failure", "type_info": "Varchar" }, { - "ordinal": 14, + "ordinal": 15, "name": "on_failure_times", "type_info": "Int4" }, { - "ordinal": 15, + "ordinal": 16, "name": "on_failure_exact", "type_info": "Bool" }, { - "ordinal": 16, + "ordinal": 17, "name": "on_failure_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 17, + "ordinal": 18, "name": "on_recovery", "type_info": "Varchar" }, { - "ordinal": 18, + "ordinal": 19, "name": "on_recovery_times", "type_info": "Int4" }, { - "ordinal": 19, + "ordinal": 20, "name": "on_recovery_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 20, + "ordinal": 21, "name": "on_success", "type_info": "Varchar" }, { - "ordinal": 21, + "ordinal": 22, "name": "on_success_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 22, + "ordinal": 23, "name": "ws_error_handler_muted", "type_info": "Bool" }, { - "ordinal": 23, + "ordinal": 24, "name": "retry", "type_info": "Jsonb" }, { - "ordinal": 24, + "ordinal": 25, "name": "no_flow_overlap", "type_info": "Bool" }, { - "ordinal": 25, + "ordinal": 26, "name": "summary", "type_info": "Varchar" }, { - "ordinal": 26, + "ordinal": 27, "name": "description", "type_info": "Text" }, { - "ordinal": 27, + "ordinal": 28, "name": "tag", "type_info": "Varchar" }, { - "ordinal": 28, + "ordinal": 29, "name": "paused_until", "type_info": "Timestamptz" }, { - "ordinal": 29, + "ordinal": 30, "name": "cron_version", "type_info": "Text" }, { - "ordinal": 30, + "ordinal": 31, "name": "dynamic_skip", "type_info": "Varchar" } @@ -180,6 +185,7 @@ true, false, false, + false, true, true, true, @@ -201,5 +207,5 @@ true ] }, - "hash": "7927b80ce75d99b2a30f6b29196af000578a3c166509f032d14452cc637d884f" + "hash": "ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91" } diff --git a/backend/.sqlx/query-74c11f5a0315424574fe3e7429f967f7b94f9d9db7be628f9cb411d789085711.json b/backend/.sqlx/query-ae8d7c07a4027bccf404951e75992bf3048c58bdf6fc67cb344eeb6cf72a9156.json similarity index 73% rename from backend/.sqlx/query-74c11f5a0315424574fe3e7429f967f7b94f9d9db7be628f9cb411d789085711.json rename to backend/.sqlx/query-ae8d7c07a4027bccf404951e75992bf3048c58bdf6fc67cb344eeb6cf72a9156.json index 1b33e89eb1..20325e03e0 100644 --- a/backend/.sqlx/query-74c11f5a0315424574fe3e7429f967f7b94f9d9db7be628f9cb411d789085711.json +++ b/backend/.sqlx/query-ae8d7c07a4027bccf404951e75992bf3048c58bdf6fc67cb344eeb6cf72a9156.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, email, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ", + "query": "\n INSERT INTO http_trigger (\n path, route_path, route_path_key, script_path, is_flow,\n workspace_id, edited_by, permissioned_as, http_method,\n authentication_method, is_static_website, workspaced_route,\n wrap_body, raw_string\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::http_method,\n $10::authentication_method, $11, $12, $13, $14)\n ", "describe": { "columns": [], "parameters": { @@ -50,5 +50,5 @@ }, "nullable": [] }, - "hash": "74c11f5a0315424574fe3e7429f967f7b94f9d9db7be628f9cb411d789085711" + "hash": "ae8d7c07a4027bccf404951e75992bf3048c58bdf6fc67cb344eeb6cf72a9156" } diff --git a/backend/.sqlx/query-e25aa749cfc9f0bb1649d162e36f2c0ce2187e47d745aa5ba96f9453c722750c.json b/backend/.sqlx/query-c301e668a5b88741e6c20700909be55518fabafcadaf6458fdcf1095643534c5.json similarity index 73% rename from backend/.sqlx/query-e25aa749cfc9f0bb1649d162e36f2c0ce2187e47d745aa5ba96f9453c722750c.json rename to backend/.sqlx/query-c301e668a5b88741e6c20700909be55518fabafcadaf6458fdcf1095643534c5.json index f5a1215aa7..cb9e48a1ab 100644 --- a/backend/.sqlx/query-e25aa749cfc9f0bb1649d162e36f2c0ce2187e47d745aa5ba96f9453c722750c.json +++ b/backend/.sqlx/query-c301e668a5b88741e6c20700909be55518fabafcadaf6458fdcf1095643534c5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO mqtt_trigger (\n mqtt_resource_path,\n subscribe_topics,\n client_version,\n client_id,\n v3_config,\n v5_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n email,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16\n )", + "query": "\n INSERT INTO mqtt_trigger (\n mqtt_resource_path,\n subscribe_topics,\n client_version,\n client_id,\n v3_config,\n v5_config,\n workspace_id,\n path,\n script_path,\n is_flow,\n permissioned_as,\n mode,\n edited_by,\n error_handler_path,\n error_handler_args,\n retry\n )\n VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16\n )", "describe": { "columns": [], "parameters": { @@ -46,5 +46,5 @@ }, "nullable": [] }, - "hash": "e25aa749cfc9f0bb1649d162e36f2c0ce2187e47d745aa5ba96f9453c722750c" + "hash": "c301e668a5b88741e6c20700909be55518fabafcadaf6458fdcf1095643534c5" } diff --git a/backend/.sqlx/query-888f7e82c25b677172a276d042ae7066113f2a522068f04c8cee0895a49b787f.json b/backend/.sqlx/query-c5b650748a4ac0d0a04e287d1b3c89a5cc2af0439fe3c3c5829fd0776ec0734e.json similarity index 78% rename from backend/.sqlx/query-888f7e82c25b677172a276d042ae7066113f2a522068f04c8cee0895a49b787f.json rename to backend/.sqlx/query-c5b650748a4ac0d0a04e287d1b3c89a5cc2af0439fe3c3c5829fd0776ec0734e.json index e8917e2b37..bf6ec3db46 100644 --- a/backend/.sqlx/query-888f7e82c25b677172a276d042ae7066113f2a522068f04c8cee0895a49b787f.json +++ b/backend/.sqlx/query-c5b650748a4ac0d0a04e287d1b3c89a5cc2af0439fe3c3c5829fd0776ec0734e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n mode = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n email = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ", + "query": "\n UPDATE\n http_trigger\n SET\n wrap_body = $1,\n raw_string = $2,\n authentication_resource_path = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n mode = $7,\n http_method = $8,\n static_asset_config = $9,\n edited_by = $10,\n permissioned_as = $11,\n request_type = $12,\n authentication_method = $13,\n summary = $14,\n description = $15,\n edited_at = now(),\n is_static_website = $16,\n error_handler_path = $17,\n error_handler_args = $18,\n retry = $19\n WHERE\n workspace_id = $20 AND\n path = $21\n ", "describe": { "columns": [], "parameters": { @@ -79,5 +79,5 @@ }, "nullable": [] }, - "hash": "888f7e82c25b677172a276d042ae7066113f2a522068f04c8cee0895a49b787f" + "hash": "c5b650748a4ac0d0a04e287d1b3c89a5cc2af0439fe3c3c5829fd0776ec0734e" } diff --git a/backend/.sqlx/query-6c3c38ed5a0e6de0c97954fe4581daa788a7551b4a00dd33f78d36e246b98dd7.json b/backend/.sqlx/query-c7aed7fe3b6774477d403bc3e7fcbce7cdbdd1feb553718cbde60bb8ccff4733.json similarity index 62% rename from backend/.sqlx/query-6c3c38ed5a0e6de0c97954fe4581daa788a7551b4a00dd33f78d36e246b98dd7.json rename to backend/.sqlx/query-c7aed7fe3b6774477d403bc3e7fcbce7cdbdd1feb553718cbde60bb8ccff4733.json index 659a783821..772b86a11c 100644 --- a/backend/.sqlx/query-6c3c38ed5a0e6de0c97954fe4581daa788a7551b4a00dd33f78d36e246b98dd7.json +++ b/backend/.sqlx/query-c7aed7fe3b6774477d403bc3e7fcbce7cdbdd1feb553718cbde60bb8ccff4733.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE\n websocket_trigger\n SET\n url = $1,\n script_path = $2,\n path = $3,\n is_flow = $4,\n filters = $5,\n initial_messages = $6,\n url_runnable_args = $7,\n edited_by = $8,\n email = $9,\n can_return_message = $10,\n can_return_error_result = $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 ", + "query": "\n UPDATE\n websocket_trigger\n SET\n url = $1,\n script_path = $2,\n path = $3,\n is_flow = $4,\n filters = $5,\n initial_messages = $6,\n url_runnable_args = $7,\n edited_by = $8,\n permissioned_as = $9,\n can_return_message = $10,\n can_return_error_result = $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": { @@ -25,5 +25,5 @@ }, "nullable": [] }, - "hash": "6c3c38ed5a0e6de0c97954fe4581daa788a7551b4a00dd33f78d36e246b98dd7" + "hash": "c7aed7fe3b6774477d403bc3e7fcbce7cdbdd1feb553718cbde60bb8ccff4733" } diff --git a/backend/.sqlx/query-8c3cc09c1bbb6209467c75723dd02e97dddb99789422012e85e86c3151a9f2e9.json b/backend/.sqlx/query-ccd76be88fa9c11b3dc2e6d7711437ab3e02d8c3c10c53decc664533b8d04bc0.json similarity index 67% rename from backend/.sqlx/query-8c3cc09c1bbb6209467c75723dd02e97dddb99789422012e85e86c3151a9f2e9.json rename to backend/.sqlx/query-ccd76be88fa9c11b3dc2e6d7711437ab3e02d8c3c10c53decc664533b8d04bc0.json index 1ef649375d..d9b7688eba 100644 --- a/backend/.sqlx/query-8c3cc09c1bbb6209467c75723dd02e97dddb99789422012e85e86c3151a9f2e9.json +++ b/backend/.sqlx/query-ccd76be88fa9c11b3dc2e6d7711437ab3e02d8c3c10c53decc664533b8d04bc0.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id,\n script_path, is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", + "query": "\n INSERT INTO kafka_trigger (\n path, kafka_resource_path, topics, group_id,\n script_path, is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ", "describe": { "columns": [], "parameters": { @@ -18,5 +18,5 @@ }, "nullable": [] }, - "hash": "8c3cc09c1bbb6209467c75723dd02e97dddb99789422012e85e86c3151a9f2e9" + "hash": "ccd76be88fa9c11b3dc2e6d7711437ab3e02d8c3c10c53decc664533b8d04bc0" } diff --git a/backend/.sqlx/query-de656102e898ebc90e53af8d36c882cf47e798567bb733e8b528dcedc48ad4bf.json b/backend/.sqlx/query-cd410aa458ad23d47945084a0b9614babda8e6f9fe1ce71224773b2d5f307161.json similarity index 68% rename from backend/.sqlx/query-de656102e898ebc90e53af8d36c882cf47e798567bb733e8b528dcedc48ad4bf.json rename to backend/.sqlx/query-cd410aa458ad23d47945084a0b9614babda8e6f9fe1ce71224773b2d5f307161.json index 6639e931ab..8beca44da9 100644 --- a/backend/.sqlx/query-de656102e898ebc90e53af8d36c882cf47e798567bb733e8b528dcedc48ad4bf.json +++ b/backend/.sqlx/query-cd410aa458ad23d47945084a0b9614babda8e6f9fe1ce71224773b2d5f307161.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE local_part = $1 \n AND workspaced_local_part = FALSE\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ", + "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n permissioned_as,\n path,\n error_handler_path as \"error_handler_path: _\",\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM email_trigger\n WHERE local_part = $1\n AND workspaced_local_part = FALSE\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ", "describe": { "columns": [ { @@ -36,31 +36,26 @@ }, { "ordinal": 4, - "name": "edited_by", + "name": "permissioned_as", "type_info": "Varchar" }, { "ordinal": 5, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 6, "name": "path", "type_info": "Varchar" }, { - "ordinal": 7, + "ordinal": 6, "name": "error_handler_path: _", "type_info": "Varchar" }, { - "ordinal": 8, + "ordinal": 7, "name": "error_handler_args: _", "type_info": "Jsonb" }, { - "ordinal": 9, + "ordinal": 8, "name": "retry: _", "type_info": "Jsonb" } @@ -77,11 +72,10 @@ false, false, false, - false, true, true, true ] }, - "hash": "de656102e898ebc90e53af8d36c882cf47e798567bb733e8b528dcedc48ad4bf" + "hash": "cd410aa458ad23d47945084a0b9614babda8e6f9fe1ce71224773b2d5f307161" } diff --git a/backend/.sqlx/query-f4340e31adddc9a37bab64957b2b1b9b29f978c73f003dfbf81d5c487f666358.json b/backend/.sqlx/query-d082ff0fcc2bb02257ab82376d87ff8535dfcb7c2b1143b7016fa17bc42a595d.json similarity index 62% rename from backend/.sqlx/query-f4340e31adddc9a37bab64957b2b1b9b29f978c73f003dfbf81d5c487f666358.json rename to backend/.sqlx/query-d082ff0fcc2bb02257ab82376d87ff8535dfcb7c2b1143b7016fa17bc42a595d.json index 42f48ab33e..07a22274ff 100644 --- a/backend/.sqlx/query-f4340e31adddc9a37bab64957b2b1b9b29f978c73f003dfbf81d5c487f666358.json +++ b/backend/.sqlx/query-d082ff0fcc2bb02257ab82376d87ff8535dfcb7c2b1143b7016fa17bc42a595d.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE postgres_trigger\n SET\n postgres_resource_path = $1,\n replication_slot_name = $2,\n publication_name = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n edited_by = $7,\n email = $8,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $11,\n error_handler_args = $12,\n retry = $13\n WHERE\n workspace_id = $9 AND path = $10\n ", + "query": "\n UPDATE postgres_trigger\n SET\n postgres_resource_path = $1,\n replication_slot_name = $2,\n publication_name = $3,\n script_path = $4,\n path = $5,\n is_flow = $6,\n edited_by = $7,\n permissioned_as = $8,\n edited_at = now(),\n server_id = NULL,\n error = NULL,\n error_handler_path = $11,\n error_handler_args = $12,\n retry = $13\n WHERE\n workspace_id = $9 AND path = $10\n ", "describe": { "columns": [], "parameters": { @@ -22,5 +22,5 @@ }, "nullable": [] }, - "hash": "f4340e31adddc9a37bab64957b2b1b9b29f978c73f003dfbf81d5c487f666358" + "hash": "d082ff0fcc2bb02257ab82376d87ff8535dfcb7c2b1143b7016fa17bc42a595d" } diff --git a/backend/.sqlx/query-8642e4fc3efb011a8b7f98d2080a165ec4046c3a7172b5635b818c0fa133411f.json b/backend/.sqlx/query-d35300ab94202c181ee5d1e1c76ec9cf1044ff173375a82c529babe053acb24f.json similarity index 66% rename from backend/.sqlx/query-8642e4fc3efb011a8b7f98d2080a165ec4046c3a7172b5635b818c0fa133411f.json rename to backend/.sqlx/query-d35300ab94202c181ee5d1e1c76ec9cf1044ff173375a82c529babe053acb24f.json index f42d028128..8f171cecf2 100644 --- a/backend/.sqlx/query-8642e4fc3efb011a8b7f98d2080a165ec4046c3a7172b5635b818c0fa133411f.json +++ b/backend/.sqlx/query-d35300ab94202c181ee5d1e1c76ec9cf1044ff173375a82c529babe053acb24f.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE\n gcp_trigger\n SET\n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6,\n edited_by = $7,\n email = $8,\n script_path = $9,\n path = $10,\n mode = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16,\n auto_acknowledge_msg = $17,\n ack_deadline = $18\n WHERE\n workspace_id = $12 AND\n path = $13\n ", + "query": "\n UPDATE\n gcp_trigger\n SET\n gcp_resource_path = $1,\n subscription_id = $2,\n topic_id = $3,\n delivery_type = $4,\n delivery_config = $5,\n is_flow = $6,\n edited_by = $7,\n permissioned_as = $8,\n script_path = $9,\n path = $10,\n mode = $11,\n edited_at = now(),\n error = NULL,\n server_id = NULL,\n error_handler_path = $14,\n error_handler_args = $15,\n retry = $16,\n auto_acknowledge_msg = $17,\n ack_deadline = $18\n WHERE\n workspace_id = $12 AND\n path = $13\n ", "describe": { "columns": [], "parameters": { @@ -48,5 +48,5 @@ }, "nullable": [] }, - "hash": "8642e4fc3efb011a8b7f98d2080a165ec4046c3a7172b5635b818c0fa133411f" + "hash": "d35300ab94202c181ee5d1e1c76ec9cf1044ff173375a82c529babe053acb24f" } diff --git a/backend/.sqlx/query-1bf8dc01326ebf6b8faa04e418b781e37bb9cedd1a89bf71a969b6db8cace48e.json b/backend/.sqlx/query-d56a8a7291ce3141c06d79ef854ac2cf970c3e41a223e31d3991276742d2afe1.json similarity index 73% rename from backend/.sqlx/query-1bf8dc01326ebf6b8faa04e418b781e37bb9cedd1a89bf71a969b6db8cace48e.json rename to backend/.sqlx/query-d56a8a7291ce3141c06d79ef854ac2cf970c3e41a223e31d3991276742d2afe1.json index e047ddf70c..78a6c3b962 100644 --- a/backend/.sqlx/query-1bf8dc01326ebf6b8faa04e418b781e37bb9cedd1a89bf71a969b6db8cace48e.json +++ b/backend/.sqlx/query-d56a8a7291ce3141c06d79ef854ac2cf970c3e41a223e31d3991276742d2afe1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO nats_trigger (\n workspace_id,\n path,\n nats_resource_path,\n subjects,\n stream_name,\n consumer_name,\n use_jetstream,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\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, now(), $13, $14, $15\n )\n ", + "query": "\n INSERT INTO nats_trigger (\n workspace_id,\n path,\n nats_resource_path,\n subjects,\n stream_name,\n consumer_name,\n use_jetstream,\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, now(), $13, $14, $15\n )\n ", "describe": { "columns": [], "parameters": { @@ -35,5 +35,5 @@ }, "nullable": [] }, - "hash": "1bf8dc01326ebf6b8faa04e418b781e37bb9cedd1a89bf71a969b6db8cace48e" + "hash": "d56a8a7291ce3141c06d79ef854ac2cf970c3e41a223e31d3991276742d2afe1" } diff --git a/backend/.sqlx/query-c659cfe81441bb1b62a9f23f5d9b7f5b1f354b6bb0a78d007114fed084cfe292.json b/backend/.sqlx/query-db9957ea9b5b326c27ff62f96a3981787a91ac587b1936c774de7a2a2094bf91.json similarity index 68% rename from backend/.sqlx/query-c659cfe81441bb1b62a9f23f5d9b7f5b1f354b6bb0a78d007114fed084cfe292.json rename to backend/.sqlx/query-db9957ea9b5b326c27ff62f96a3981787a91ac587b1936c774de7a2a2094bf91.json index 7036bdbb46..0b45a6d4b7 100644 --- a/backend/.sqlx/query-c659cfe81441bb1b62a9f23f5d9b7f5b1f354b6bb0a78d007114fed084cfe292.json +++ b/backend/.sqlx/query-db9957ea9b5b326c27ff62f96a3981787a91ac587b1936c774de7a2a2094bf91.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO sqs_trigger (\n path, queue_url, aws_resource_path, script_path,\n is_flow, workspace_id, edited_by, email\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", + "query": "\n INSERT INTO sqs_trigger (\n path, queue_url, aws_resource_path, script_path,\n is_flow, workspace_id, edited_by, permissioned_as\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", "describe": { "columns": [], "parameters": { @@ -17,5 +17,5 @@ }, "nullable": [] }, - "hash": "c659cfe81441bb1b62a9f23f5d9b7f5b1f354b6bb0a78d007114fed084cfe292" + "hash": "db9957ea9b5b326c27ff62f96a3981787a91ac587b1936c774de7a2a2094bf91" } diff --git a/backend/.sqlx/query-075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a.json b/backend/.sqlx/query-dbb16284b9dd98b9339816e43eebf0fef488102cd7b3fd38d8af3148545bf1a4.json similarity index 63% rename from backend/.sqlx/query-075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a.json rename to backend/.sqlx/query-dbb16284b9dd98b9339816e43eebf0fef488102cd7b3fd38d8af3148545bf1a4.json index 6b42ce5735..c2cf53ef4c 100644 --- a/backend/.sqlx/query-075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a.json +++ b/backend/.sqlx/query-dbb16284b9dd98b9339816e43eebf0fef488102cd7b3fd38d8af3148545bf1a4.json @@ -1,11 +1,11 @@ { "db_name": "PostgreSQL", - "query": "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "query": "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", "describe": { "columns": [ { "ordinal": 0, - "name": "email", + "name": "permissioned_as", "type_info": "Varchar" }, { @@ -25,5 +25,5 @@ false ] }, - "hash": "075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a" + "hash": "dbb16284b9dd98b9339816e43eebf0fef488102cd7b3fd38d8af3148545bf1a4" } diff --git a/backend/.sqlx/query-23e4c6e3dc6a48f702c2b26a6b1f94668e086caaa0093a3b685f87483513b0d2.json b/backend/.sqlx/query-dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e.json similarity index 69% rename from backend/.sqlx/query-23e4c6e3dc6a48f702c2b26a6b1f94668e086caaa0093a3b685f87483513b0d2.json rename to backend/.sqlx/query-dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e.json index fe2b1e3427..5b50558a5a 100644 --- a/backend/.sqlx/query-23e4c6e3dc6a48f702c2b26a6b1f94668e086caaa0093a3b685f87483513b0d2.json +++ b/backend/.sqlx/query-dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description, dynamic_skip\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10,\n $11, $12, $13, $14,\n $15, $16, $17,\n $18, $19,\n $20, $21, $22, $23,\n $24, $25, $26, $27, $28\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ", + "query": "\n INSERT INTO schedule (\n workspace_id, path, schedule, timezone, edited_by, script_path,\n is_flow, args, enabled, email, permissioned_as,\n on_failure, on_failure_times, on_failure_exact, on_failure_extra_args,\n on_recovery, on_recovery_times, on_recovery_extra_args,\n on_success, on_success_extra_args,\n ws_error_handler_muted, retry, summary, no_flow_overlap,\n tag, paused_until, cron_version, description, dynamic_skip\n ) VALUES (\n $1, $2, $3, $4, $5, $6,\n $7, $8, $9, $10, $11,\n $12, $13, $14, $15,\n $16, $17, $18,\n $19, $20,\n $21, $22, $23, $24,\n $25, $26, $27, $28, $29\n )\n RETURNING\n workspace_id,\n path,\n edited_by,\n edited_at,\n schedule,\n timezone,\n enabled,\n script_path,\n is_flow,\n args AS \"args: _\",\n extra_perms,\n email,\n permissioned_as,\n error,\n on_failure,\n on_failure_times,\n on_failure_exact,\n on_failure_extra_args AS \"on_failure_extra_args: _\",\n on_recovery,\n on_recovery_times,\n on_recovery_extra_args AS \"on_recovery_extra_args: _\",\n on_success,\n on_success_extra_args AS \"on_success_extra_args: _\",\n ws_error_handler_muted,\n retry,\n no_flow_overlap,\n summary,\n description,\n tag,\n paused_until,\n cron_version,\n dynamic_skip\n ", "describe": { "columns": [ { @@ -65,96 +65,101 @@ }, { "ordinal": 12, + "name": "permissioned_as", + "type_info": "Varchar" + }, + { + "ordinal": 13, "name": "error", "type_info": "Text" }, { - "ordinal": 13, + "ordinal": 14, "name": "on_failure", "type_info": "Varchar" }, { - "ordinal": 14, + "ordinal": 15, "name": "on_failure_times", "type_info": "Int4" }, { - "ordinal": 15, + "ordinal": 16, "name": "on_failure_exact", "type_info": "Bool" }, { - "ordinal": 16, + "ordinal": 17, "name": "on_failure_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 17, + "ordinal": 18, "name": "on_recovery", "type_info": "Varchar" }, { - "ordinal": 18, + "ordinal": 19, "name": "on_recovery_times", "type_info": "Int4" }, { - "ordinal": 19, + "ordinal": 20, "name": "on_recovery_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 20, + "ordinal": 21, "name": "on_success", "type_info": "Varchar" }, { - "ordinal": 21, + "ordinal": 22, "name": "on_success_extra_args: _", "type_info": "Jsonb" }, { - "ordinal": 22, + "ordinal": 23, "name": "ws_error_handler_muted", "type_info": "Bool" }, { - "ordinal": 23, + "ordinal": 24, "name": "retry", "type_info": "Jsonb" }, { - "ordinal": 24, + "ordinal": 25, "name": "no_flow_overlap", "type_info": "Bool" }, { - "ordinal": 25, + "ordinal": 26, "name": "summary", "type_info": "Varchar" }, { - "ordinal": 26, + "ordinal": 27, "name": "description", "type_info": "Text" }, { - "ordinal": 27, + "ordinal": 28, "name": "tag", "type_info": "Varchar" }, { - "ordinal": 28, + "ordinal": 29, "name": "paused_until", "type_info": "Timestamptz" }, { - "ordinal": 29, + "ordinal": 30, "name": "cron_version", "type_info": "Text" }, { - "ordinal": 30, + "ordinal": 31, "name": "dynamic_skip", "type_info": "Varchar" } @@ -172,6 +177,7 @@ "Bool", "Varchar", "Varchar", + "Varchar", "Int4", "Bool", "Jsonb", @@ -204,6 +210,7 @@ true, false, false, + false, true, true, true, @@ -225,5 +232,5 @@ true ] }, - "hash": "23e4c6e3dc6a48f702c2b26a6b1f94668e086caaa0093a3b685f87483513b0d2" + "hash": "dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e" } diff --git a/backend/.sqlx/query-cad914554762b6be72b289814f77072600f8f1bc1fa73ef6f63972775aedf044.json b/backend/.sqlx/query-de4879b8e3002ca48005406c4e107df2663aca35b60e3c528f564da3114f0af1.json similarity index 73% rename from backend/.sqlx/query-cad914554762b6be72b289814f77072600f8f1bc1fa73ef6f63972775aedf044.json rename to backend/.sqlx/query-de4879b8e3002ca48005406c4e107df2663aca35b60e3c528f564da3114f0af1.json index 1c2146bfce..a454b2f79e 100644 --- a/backend/.sqlx/query-cad914554762b6be72b289814f77072600f8f1bc1fa73ef6f63972775aedf044.json +++ b/backend/.sqlx/query-de4879b8e3002ca48005406c4e107df2663aca35b60e3c528f564da3114f0af1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO sqs_trigger (\n workspace_id,\n path,\n queue_url,\n aws_resource_path,\n message_attributes,\n aws_auth_resource_type,\n script_path,\n is_flow,\n mode,\n edited_by,\n email,\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, now(), $12, $13, $14\n )\n ", + "query": "\n INSERT INTO sqs_trigger (\n workspace_id,\n path,\n queue_url,\n aws_resource_path,\n message_attributes,\n aws_auth_resource_type,\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, now(), $12, $13, $14\n )\n ", "describe": { "columns": [], "parameters": { @@ -44,5 +44,5 @@ }, "nullable": [] }, - "hash": "cad914554762b6be72b289814f77072600f8f1bc1fa73ef6f63972775aedf044" + "hash": "de4879b8e3002ca48005406c4e107df2663aca35b60e3c528f564da3114f0af1" } diff --git a/backend/.sqlx/query-3aad6340ea1f8dc8411742ef5de5a77d5de903845da9e686aef804bc09db1687.json b/backend/.sqlx/query-e3f09fe777cbbc009ca1af31989b542ec2d585ef672f0e1496944f4dd0d082f4.json similarity index 67% rename from backend/.sqlx/query-3aad6340ea1f8dc8411742ef5de5a77d5de903845da9e686aef804bc09db1687.json rename to backend/.sqlx/query-e3f09fe777cbbc009ca1af31989b542ec2d585ef672f0e1496944f4dd0d082f4.json index a93a137b5f..b8e8a90bc3 100644 --- a/backend/.sqlx/query-3aad6340ea1f8dc8411742ef5de5a77d5de903845da9e686aef804bc09db1687.json +++ b/backend/.sqlx/query-e3f09fe777cbbc009ca1af31989b542ec2d585ef672f0e1496944f4dd0d082f4.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n edited_by, \n email, \n path, \n error_handler_path as \"error_handler_path: _\", \n error_handler_args as \"error_handler_args: _\", \n retry as \"retry: _\" \n FROM email_trigger \n WHERE workspace_id = $1 \n AND local_part = $2 \n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ", + "query": "\n SELECT \n script_path, \n is_flow, \n workspace_id, \n mode as \"mode: _\",\n permissioned_as,\n path,\n error_handler_path as \"error_handler_path: _\",\n error_handler_args as \"error_handler_args: _\",\n retry as \"retry: _\"\n FROM email_trigger\n WHERE workspace_id = $1\n AND local_part = $2\n AND (workspaced_local_part = TRUE OR $3 IS TRUE)\n AND (mode = 'enabled'::TRIGGER_MODE OR mode = 'suspended'::TRIGGER_MODE)\n ", "describe": { "columns": [ { @@ -36,31 +36,26 @@ }, { "ordinal": 4, - "name": "edited_by", + "name": "permissioned_as", "type_info": "Varchar" }, { "ordinal": 5, - "name": "email", - "type_info": "Varchar" - }, - { - "ordinal": 6, "name": "path", "type_info": "Varchar" }, { - "ordinal": 7, + "ordinal": 6, "name": "error_handler_path: _", "type_info": "Varchar" }, { - "ordinal": 8, + "ordinal": 7, "name": "error_handler_args: _", "type_info": "Jsonb" }, { - "ordinal": 9, + "ordinal": 8, "name": "retry: _", "type_info": "Jsonb" } @@ -79,11 +74,10 @@ false, false, false, - false, true, true, true ] }, - "hash": "3aad6340ea1f8dc8411742ef5de5a77d5de903845da9e686aef804bc09db1687" + "hash": "e3f09fe777cbbc009ca1af31989b542ec2d585ef672f0e1496944f4dd0d082f4" } diff --git a/backend/.sqlx/query-e998bb8195b4fafe6de20fb0bf4315b3a10db9aca72447b8b8afd9474baa0614.json b/backend/.sqlx/query-e998bb8195b4fafe6de20fb0bf4315b3a10db9aca72447b8b8afd9474baa0614.json new file mode 100644 index 0000000000..de94a70224 --- /dev/null +++ b/backend/.sqlx/query-e998bb8195b4fafe6de20fb0bf4315b3a10db9aca72447b8b8afd9474baa0614.json @@ -0,0 +1,18 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO kafka_trigger (path, kafka_resource_path, group_id, topics, script_path, is_flow, workspace_id, edited_by, permissioned_as) VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "VarcharArray", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "e998bb8195b4fafe6de20fb0bf4315b3a10db9aca72447b8b8afd9474baa0614" +} diff --git a/backend/.sqlx/query-e485c82978d10379c6d1b7cd850f3bf764e6c5bf775f3d710ba36a79f5e424eb.json b/backend/.sqlx/query-f57a891d1b507ec6767099a388be7deb97a89168fd7f74040e73033075140655.json similarity index 68% rename from backend/.sqlx/query-e485c82978d10379c6d1b7cd850f3bf764e6c5bf775f3d710ba36a79f5e424eb.json rename to backend/.sqlx/query-f57a891d1b507ec6767099a388be7deb97a89168fd7f74040e73033075140655.json index fc85891f33..2da629d706 100644 --- a/backend/.sqlx/query-e485c82978d10379c6d1b7cd850f3bf764e6c5bf775f3d710ba36a79f5e424eb.json +++ b/backend/.sqlx/query-f57a891d1b507ec6767099a388be7deb97a89168fd7f74040e73033075140655.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, email, initial_messages\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", + "query": "\n INSERT INTO websocket_trigger (\n path, url, script_path, is_flow, workspace_id,\n edited_by, permissioned_as, initial_messages\n )\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", "describe": { "columns": [], "parameters": { @@ -17,5 +17,5 @@ }, "nullable": [] }, - "hash": "e485c82978d10379c6d1b7cd850f3bf764e6c5bf775f3d710ba36a79f5e424eb" + "hash": "f57a891d1b507ec6767099a388be7deb97a89168fd7f74040e73033075140655" } diff --git a/backend/.sqlx/query-f5f18e6c5b7376b2760915ee3c859fb2306f16c0c601668adaf30baf66f0fd0d.json b/backend/.sqlx/query-f5f18e6c5b7376b2760915ee3c859fb2306f16c0c601668adaf30baf66f0fd0d.json new file mode 100644 index 0000000000..3f7f7b6111 --- /dev/null +++ b/backend/.sqlx/query-f5f18e6c5b7376b2760915ee3c859fb2306f16c0c601668adaf30baf66f0fd0d.json @@ -0,0 +1,17 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO sqs_trigger (path, queue_url, aws_resource_path, script_path, is_flow, workspace_id, edited_by, permissioned_as) VALUES ($1, $2, $3, $4, false, 'test-workspace', 'test-user', 'u/test-user')", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "f5f18e6c5b7376b2760915ee3c859fb2306f16c0c601668adaf30baf66f0fd0d" +} diff --git a/backend/Cargo.lock b/backend/Cargo.lock index c5ed1068b3..87cc86b0b9 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -15787,6 +15787,7 @@ dependencies = [ "uuid", "windmill-api", "windmill-api-agent-workers", + "windmill-api-auth", "windmill-api-client", "windmill-api-settings", "windmill-autoscaling", @@ -15800,6 +15801,14 @@ dependencies = [ "windmill-queue", "windmill-runtime-nativets", "windmill-test-utils", + "windmill-trigger", + "windmill-trigger-gcp", + "windmill-trigger-kafka", + "windmill-trigger-mqtt", + "windmill-trigger-nats", + "windmill-trigger-postgres", + "windmill-trigger-sqs", + "windmill-trigger-websocket", "windmill-types", "windmill-worker", "windmill-worker-volumes", @@ -16477,6 +16486,7 @@ dependencies = [ "cron", "croner", "datafusion", + "equivalent", "futures", "futures-core", "gethostname", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index afc2faf1f0..a44c72729b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -260,6 +260,15 @@ windmill-dep-map.workspace = true windmill-test-utils.workspace = true windmill-worker-volumes.workspace = true windmill-types.workspace = true +windmill-trigger.workspace = true +windmill-trigger-websocket.workspace = true +windmill-trigger-postgres.workspace = true +windmill-trigger-mqtt.workspace = true +windmill-trigger-kafka.workspace = true +windmill-trigger-nats.workspace = true +windmill-trigger-sqs.workspace = true +windmill-trigger-gcp.workspace = true +windmill-api-auth.workspace = true axum.workspace = true serde.workspace = true windmill-api-client.workspace = true diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 2a98a064bd..4700f99316 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -b3b8005d45e3f2aa7228c61d2e4ae86a17d89a30 +bda51bc33bcb573659e7ff07d0a23ff6e23b8148 diff --git a/backend/migrations/20260318000000_add_permissioned_as.down.sql b/backend/migrations/20260318000000_add_permissioned_as.down.sql new file mode 100644 index 0000000000..135955bfb7 --- /dev/null +++ b/backend/migrations/20260318000000_add_permissioned_as.down.sql @@ -0,0 +1,30 @@ +-- Revert: re-add email columns to trigger tables, drop permissioned_as + +ALTER TABLE http_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE http_trigger DROP COLUMN permissioned_as; + +ALTER TABLE websocket_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE websocket_trigger DROP COLUMN permissioned_as; + +ALTER TABLE postgres_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE postgres_trigger DROP COLUMN permissioned_as; + +ALTER TABLE mqtt_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE mqtt_trigger DROP COLUMN permissioned_as; + +ALTER TABLE kafka_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE kafka_trigger DROP COLUMN permissioned_as; + +ALTER TABLE nats_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE nats_trigger DROP COLUMN permissioned_as; + +ALTER TABLE sqs_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE sqs_trigger DROP COLUMN permissioned_as; + +ALTER TABLE gcp_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE gcp_trigger DROP COLUMN permissioned_as; + +ALTER TABLE email_trigger ADD COLUMN email VARCHAR(255) NOT NULL DEFAULT ''; +ALTER TABLE email_trigger DROP COLUMN permissioned_as; + +ALTER TABLE schedule DROP COLUMN permissioned_as; diff --git a/backend/migrations/20260318000000_add_permissioned_as.up.sql b/backend/migrations/20260318000000_add_permissioned_as.up.sql new file mode 100644 index 0000000000..20ab3bd404 --- /dev/null +++ b/backend/migrations/20260318000000_add_permissioned_as.up.sql @@ -0,0 +1,64 @@ +-- Add permissioned_as column to all trigger tables and schedule +-- permissioned_as stores 'u/{username}', 'g/{group}', or raw email +-- We add nullable first, populate, then set NOT NULL to avoid a DEFAULT '' that could mask bugs. + +-- Trigger tables: add permissioned_as, drop email +ALTER TABLE http_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE http_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE http_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE http_trigger DROP COLUMN email; + +ALTER TABLE websocket_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE websocket_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE websocket_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE websocket_trigger DROP COLUMN email; + +ALTER TABLE postgres_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE postgres_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE postgres_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE postgres_trigger DROP COLUMN email; + +ALTER TABLE mqtt_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE mqtt_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE mqtt_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE mqtt_trigger DROP COLUMN email; + +ALTER TABLE kafka_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE kafka_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE kafka_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE kafka_trigger DROP COLUMN email; + +ALTER TABLE nats_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE nats_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE nats_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE nats_trigger DROP COLUMN email; + +ALTER TABLE sqs_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE sqs_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE sqs_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE sqs_trigger DROP COLUMN email; + +ALTER TABLE gcp_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE gcp_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE gcp_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE gcp_trigger DROP COLUMN email; + +ALTER TABLE email_trigger ADD COLUMN permissioned_as VARCHAR(255); +UPDATE email_trigger SET permissioned_as = CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END; +ALTER TABLE email_trigger ALTER COLUMN permissioned_as SET NOT NULL; +ALTER TABLE email_trigger DROP COLUMN email; + +-- Schedule table: add permissioned_as, keep email for backwards compat with old workers +-- For superadmin-owned schedules, use the email to find the actual username (since edited_by +-- may have been overwritten by a later edit). Otherwise use edited_by as the source. +ALTER TABLE schedule ADD COLUMN permissioned_as VARCHAR(255); +UPDATE schedule SET permissioned_as = CASE + WHEN EXISTS ( + SELECT 1 FROM password p WHERE p.email = schedule.email AND p.super_admin = true + ) THEN COALESCE( + 'u/' || (SELECT u.username FROM usr u WHERE u.email = schedule.email AND u.workspace_id = schedule.workspace_id LIMIT 1), + schedule.email + ) + ELSE CASE WHEN edited_by LIKE '%@%' THEN edited_by ELSE 'u/' || edited_by END +END; +ALTER TABLE schedule ALTER COLUMN permissioned_as SET NOT NULL; diff --git a/backend/tests/fixtures/permissions_test.sql b/backend/tests/fixtures/permissions_test.sql index 66f406c08c..343fa81a30 100644 --- a/backend/tests/fixtures/permissions_test.sql +++ b/backend/tests/fixtures/permissions_test.sql @@ -204,14 +204,14 @@ VALUES ('test-workspace', 'u/bob/my_variable', 'bob_value', false, -- ============================================ -- Alice's personal schedule -INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms) +INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as) VALUES ('test-workspace', 'u/alice/my_schedule', 'alice', NOW(), '0 * * * *', false, - 'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}'); + 'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}', 'u/alice'); -- Bob's personal schedule -INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms) +INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as) VALUES ('test-workspace', 'u/bob/my_schedule', 'bob', NOW(), '0 * * * *', false, - 'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}'); + 'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}', 'u/bob'); -- ============================================ -- APPS (with app_versions) diff --git a/backend/tests/fixtures/preserve_on_behalf_of.sql b/backend/tests/fixtures/preserve_on_behalf_of.sql index 7467843e80..ff8428a1c5 100644 --- a/backend/tests/fixtures/preserve_on_behalf_of.sql +++ b/backend/tests/fixtures/preserve_on_behalf_of.sql @@ -65,6 +65,11 @@ INSERT INTO usr_to_group(workspace_id, group_, usr) VALUES ('test-workspace', 'wm_deployers', 'deployer-user') ON CONFLICT DO NOTHING; +-- Superadmin NOT in workspace (only in password table, not in usr) +INSERT INTO password(email, password_hash, login_type, super_admin, verified, name, username) + VALUES ('superadmin-external@windmill.dev', 'not-a-real-hash', 'password', true, true, 'External Superadmin', 'superadmin-external') +ON CONFLICT DO NOTHING; + -- Tokens for all users (token_hash = sha256 hex, token_prefix = first 10 chars) -- NOTE: plaintext `token` column is included for backward compat during transition. -- Remove it once the `token` column is dropped from the schema. @@ -80,6 +85,9 @@ ON CONFLICT DO NOTHING; INSERT INTO token(token_hash, token_prefix, token, email, label, super_admin) VALUES (encode(sha256('ORIGINAL_TOKEN'::bytea), 'hex'), 'ORIGINAL_T', 'ORIGINAL_TOKEN', 'original@windmill.dev', 'original token', false) ON CONFLICT DO NOTHING; +INSERT INTO token(token_hash, token_prefix, token, email, label, super_admin) +VALUES (encode(sha256('EXTERNAL_SUPERADMIN_TOKEN'::bytea), 'hex'), 'EXTERNAL_S', 'EXTERNAL_SUPERADMIN_TOKEN', 'superadmin-external@windmill.dev', 'external superadmin token', true) +ON CONFLICT DO NOTHING; GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_admin; GRANT ALL PRIVILEGES ON TABLE workspace_key TO windmill_user; diff --git a/backend/tests/preserve_on_behalf_of.rs b/backend/tests/preserve_on_behalf_of.rs index b70cd4148f..7ed5fc7697 100644 --- a/backend/tests/preserve_on_behalf_of.rs +++ b/backend/tests/preserve_on_behalf_of.rs @@ -123,7 +123,7 @@ fn new_http_trigger( path: &str, script_path: &str, route_path: &str, - email: Option<&str>, + permissioned_as: Option<&str>, preserve: bool, ) -> serde_json::Value { let mut trigger = json!({ @@ -139,11 +139,11 @@ fn new_http_trigger( "wrap_body": false, "raw_string": false }); - if let Some(e) = email { - trigger["email"] = json!(e); + if let Some(e) = permissioned_as { + trigger["permissioned_as"] = json!(e); } if preserve { - trigger["preserve_email"] = json!(true); + trigger["preserve_permissioned_as"] = json!(true); } trigger } @@ -156,7 +156,7 @@ fn new_http_trigger( fn new_websocket_trigger( path: &str, script_path: &str, - email: Option<&str>, + permissioned_as: Option<&str>, preserve: bool, ) -> serde_json::Value { let mut trigger = json!({ @@ -168,11 +168,11 @@ fn new_websocket_trigger( "can_return_message": false, "can_return_error_result": false }); - if let Some(e) = email { - trigger["email"] = json!(e); + if let Some(e) = permissioned_as { + trigger["permissioned_as"] = json!(e); } if preserve { - trigger["preserve_email"] = json!(true); + trigger["preserve_permissioned_as"] = json!(true); } trigger } @@ -558,8 +558,8 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { "script_path": "u/test-user/scheduled_script", "is_flow": false, "enabled": false, - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -571,7 +571,7 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { ); let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/test-user/schedule_admin_preserve", "test-workspace" ) @@ -582,8 +582,12 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { "Admin should preserve schedule email" ); assert_eq!( - schedule.edited_by, "original-user", - "Admin should preserve schedule edited_by (looked up from email)" + schedule.permissioned_as, "u/original-user", + "Admin should preserve schedule permissioned_as" + ); + assert_eq!( + schedule.edited_by, "test-user", + "edited_by should be the deploying user (admin)" ); // ======================================== @@ -620,8 +624,8 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { "script_path": "u/deployer-user/scheduled_script", "is_flow": false, "enabled": false, - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -633,7 +637,7 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { ); let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/deployer-user/schedule_deployer_preserve", "test-workspace" ) @@ -644,8 +648,12 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { "Deployer should preserve schedule email" ); assert_eq!( - schedule.edited_by, "original-user", - "Deployer should preserve schedule edited_by" + schedule.permissioned_as, "u/original-user", + "Deployer should preserve schedule permissioned_as" + ); + assert_eq!( + schedule.edited_by, "deployer-user", + "edited_by should be the deploying user (deployer)" ); // ======================================== @@ -682,8 +690,8 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { "script_path": "u/test-user-2/scheduled_script", "is_flow": false, "enabled": false, - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -695,7 +703,7 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { ); let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/test-user-2/schedule_no_preserve", "test-workspace" ) @@ -705,6 +713,10 @@ async fn test_preserve_on_behalf_of(db: Pool) -> anyhow::Result<()> { schedule.email, "test2@windmill.dev", "Non-admin should have their own email" ); + assert_eq!( + schedule.permissioned_as, "u/test-user-2", + "Non-admin should have their own permissioned_as" + ); assert_eq!( schedule.edited_by, "test-user-2", "Non-admin should have their own username as edited_by" @@ -1419,13 +1431,14 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res // Verify initial state let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/original-user/schedule_to_update", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!(schedule.email, "original@windmill.dev"); + assert_eq!(schedule.permissioned_as, "u/original-user"); assert_eq!(schedule.edited_by, "original-user"); // Admin updates with preserve flag @@ -1438,8 +1451,8 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res .json(&json!({ "schedule": "0 0 */12 * * *", "timezone": "UTC", - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -1451,7 +1464,7 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res ); let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/original-user/schedule_to_update", "test-workspace" ) @@ -1459,11 +1472,15 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res .await?; assert_eq!( schedule.email, "original@windmill.dev", - "Admin update should preserve schedule email" + "Admin update should preserve schedule email for backwards compat" ); assert_eq!( - schedule.edited_by, "original-user", - "Admin update should preserve schedule edited_by" + schedule.permissioned_as, "u/original-user", + "Admin update should preserve schedule permissioned_as" + ); + assert_eq!( + schedule.edited_by, "test-user", + "edited_by should be the deploying user (admin)" ); // ======================================== @@ -1501,8 +1518,8 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res "script_path": "u/deployer-user/sched_deploy_script", "is_flow": false, "enabled": false, - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -1523,8 +1540,8 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res .json(&json!({ "schedule": "0 0 */8 * * *", "timezone": "UTC", - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -1536,7 +1553,7 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res ); let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/deployer-user/schedule_deploy_update", "test-workspace" ) @@ -1544,11 +1561,15 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res .await?; assert_eq!( schedule.email, "original@windmill.dev", - "Deployer update should preserve schedule email" + "Deployer update should preserve schedule email for backwards compat" ); assert_eq!( - schedule.edited_by, "original-user", - "Deployer update should preserve schedule edited_by" + schedule.permissioned_as, "u/original-user", + "Deployer update should preserve schedule permissioned_as" + ); + assert_eq!( + schedule.edited_by, "deployer-user", + "edited_by should be the deploying user (deployer)" ); // ======================================== @@ -1586,8 +1607,8 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res "script_path": "u/test-user-2/sched_nonadmin_script", "is_flow": false, "enabled": false, - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -1608,8 +1629,8 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res .json(&json!({ "schedule": "0 0 */4 * * *", "timezone": "UTC", - "email": "original-user", - "preserve_email": true + "permissioned_as": "u/original-user", + "preserve_permissioned_as": true })) .send() .await?; @@ -1621,17 +1642,21 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res ); let schedule = sqlx::query!( - "SELECT email, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", "u/test-user-2/schedule_nonadmin_update", "test-workspace" ) .fetch_one(&db) .await?; - // When preserve is denied, resolve_email returns the authed user's email + // When preserve is denied, resolve_permissioned_as uses the authed user's values assert_eq!( schedule.email, "test2@windmill.dev", "Non-admin update should overwrite schedule email with their own" ); + assert_eq!( + schedule.permissioned_as, "u/test-user-2", + "Non-admin update should overwrite schedule permissioned_as with their own" + ); Ok(()) } @@ -1639,15 +1664,15 @@ async fn test_schedule_update_preserves_email(db: Pool) -> anyhow::Res // ============================================================================ // HTTP Trigger Tests // ============================================================================ -// All trigger types share the same BaseTriggerData.resolve_email() and -// resolve_edited_by() code path. Testing HTTP triggers validates the +// All trigger types share the same BaseTriggerData.resolve_permissioned_as() +// and resolve_edited_by() code path. Testing HTTP triggers validates the // preservation logic for all trigger types (WebSocket, MQTT, PostgreSQL, // Kafka, NATS, SQS, GCP, Email). -/// HTTP Trigger: admin preserve_email tests (HTTP triggers require admin) +/// HTTP Trigger: admin preserve_permissioned_as tests (HTTP triggers require admin) #[cfg(feature = "http_trigger")] #[sqlx::test(fixtures("preserve_on_behalf_of"))] -async fn test_http_trigger_preserve_email(db: Pool) -> anyhow::Result<()> { +async fn test_http_trigger_preserve_permissioned_as(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; @@ -1685,7 +1710,7 @@ async fn test_http_trigger_preserve_email(db: Pool) -> anyhow::Result< "u/test-user/http_admin_preserve", "u/test-user/trigger_script", "admin-preserve", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -1698,19 +1723,19 @@ async fn test_http_trigger_preserve_email(db: Pool) -> anyhow::Result< ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user/http_admin_preserve", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "original@windmill.dev", + trigger.permissioned_as, "u/original-user", "Admin should preserve http trigger email" ); assert_eq!( - trigger.edited_by, "original-user", - "Admin should preserve http trigger edited_by" + trigger.edited_by, "test-user", + "edited_by should be the deploying user (admin)" ); // ======================================== @@ -1725,7 +1750,7 @@ async fn test_http_trigger_preserve_email(db: Pool) -> anyhow::Result< "u/test-user/http_no_flag", "u/test-user/trigger_script", "no-flag", - Some("original-user"), + Some("u/original-user"), false, )) .send() @@ -1738,14 +1763,14 @@ async fn test_http_trigger_preserve_email(db: Pool) -> anyhow::Result< ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user/http_no_flag", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "test@windmill.dev", + trigger.permissioned_as, "u/test-user", "Without preserve flag, admin's own email should be used" ); assert_eq!( @@ -1794,7 +1819,7 @@ async fn test_http_trigger_update_preserves_email(db: Pool) -> anyhow: "u/test-user/http_to_update", "u/test-user/http_update_script", "to-update", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -1808,14 +1833,14 @@ async fn test_http_trigger_update_preserves_email(db: Pool) -> anyhow: // Verify initial state let trigger = sqlx::query!( - "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user/http_to_update", "test-workspace" ) .fetch_one(&db) .await?; - assert_eq!(trigger.email, "original@windmill.dev"); - assert_eq!(trigger.edited_by, "original-user"); + assert_eq!(trigger.permissioned_as, "u/original-user"); + assert_eq!(trigger.edited_by, "test-user"); // Admin updates with preserve flag let resp = authed( @@ -1828,7 +1853,7 @@ async fn test_http_trigger_update_preserves_email(db: Pool) -> anyhow: "u/test-user/http_to_update", "u/test-user/http_update_script", "to-update", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -1841,19 +1866,19 @@ async fn test_http_trigger_update_preserves_email(db: Pool) -> anyhow: ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user/http_to_update", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "original@windmill.dev", + trigger.permissioned_as, "u/original-user", "Admin update should preserve http trigger email" ); assert_eq!( - trigger.edited_by, "original-user", - "Admin update should preserve http trigger edited_by" + trigger.edited_by, "test-user", + "edited_by should be the deploying user (admin)" ); Ok(()) @@ -1863,10 +1888,10 @@ async fn test_http_trigger_update_preserves_email(db: Pool) -> anyhow: // WebSocket Trigger Tests // ============================================================================ -/// WebSocket Trigger: admin, deployer, and non-admin preserve_email tests +/// WebSocket Trigger: admin, deployer, and non-admin preserve_permissioned_as tests #[cfg(feature = "websocket")] #[sqlx::test(fixtures("preserve_on_behalf_of"))] -async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Result<()> { +async fn test_websocket_trigger_preserve_permissioned_as(db: Pool) -> anyhow::Result<()> { initialize_tracing().await; let server = ApiServer::start(db.clone()).await?; @@ -1902,7 +1927,7 @@ async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Re .json(&new_websocket_trigger( "u/test-user/ws_admin_preserve", "u/test-user/ws_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -1915,19 +1940,19 @@ async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Re ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user/ws_admin_preserve", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "original@windmill.dev", + trigger.permissioned_as, "u/original-user", "Admin should preserve websocket trigger email" ); assert_eq!( - trigger.edited_by, "original-user", - "Admin should preserve websocket trigger edited_by" + trigger.edited_by, "test-user", + "edited_by should be the deploying user (admin)" ); // ======================================== @@ -1941,7 +1966,7 @@ async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Re .json(&new_websocket_trigger( "u/deployer-user/ws_deployer_preserve", "u/deployer-user/ws_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -1954,19 +1979,19 @@ async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Re ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/deployer-user/ws_deployer_preserve", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "original@windmill.dev", + trigger.permissioned_as, "u/original-user", "Deployer should preserve websocket trigger email" ); assert_eq!( - trigger.edited_by, "original-user", - "Deployer should preserve websocket trigger edited_by" + trigger.edited_by, "deployer-user", + "edited_by should be the deploying user (deployer)" ); // ======================================== @@ -1980,7 +2005,7 @@ async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Re .json(&new_websocket_trigger( "u/test-user-2/ws_no_preserve", "u/test-user-2/ws_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -1993,14 +2018,14 @@ async fn test_websocket_trigger_preserve_email(db: Pool) -> anyhow::Re ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user-2/ws_no_preserve", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "test2@windmill.dev", + trigger.permissioned_as, "u/test-user-2", "Non-admin should have their own email" ); assert_eq!( @@ -2062,13 +2087,13 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an // Verify initial state let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/original-user/ws_to_update", "test-workspace" ) .fetch_one(&db) .await?; - assert_eq!(trigger.email, "original@windmill.dev"); + assert_eq!(trigger.permissioned_as, "u/original-user"); assert_eq!(trigger.edited_by, "original-user"); // Admin updates with preserve flag @@ -2081,7 +2106,7 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an .json(&new_websocket_trigger( "u/original-user/ws_to_update", "u/original-user/ws_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -2094,19 +2119,19 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/original-user/ws_to_update", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "original@windmill.dev", + trigger.permissioned_as, "u/original-user", "Admin update should preserve websocket trigger email" ); assert_eq!( - trigger.edited_by, "original-user", - "Admin update should preserve websocket trigger edited_by" + trigger.edited_by, "test-user", + "edited_by should be the deploying user (admin)" ); // ======================================== @@ -2140,7 +2165,7 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an .json(&new_websocket_trigger( "u/deployer-user/ws_deploy_update", "u/deployer-user/ws_deploy_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -2162,7 +2187,7 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an .json(&new_websocket_trigger( "u/deployer-user/ws_deploy_update", "u/deployer-user/ws_deploy_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -2175,19 +2200,19 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/deployer-user/ws_deploy_update", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "original@windmill.dev", + trigger.permissioned_as, "u/original-user", "Deployer update should preserve websocket trigger email" ); assert_eq!( - trigger.edited_by, "original-user", - "Deployer update should preserve websocket trigger edited_by" + trigger.edited_by, "deployer-user", + "edited_by should be the deploying user (deployer)" ); // ======================================== @@ -2221,7 +2246,7 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an .json(&new_websocket_trigger( "u/test-user-2/ws_nonadmin_update", "u/test-user-2/ws_nonadmin_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -2243,7 +2268,7 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an .json(&new_websocket_trigger( "u/test-user-2/ws_nonadmin_update", "u/test-user-2/ws_nonadmin_script", - Some("original-user"), + Some("u/original-user"), true, )) .send() @@ -2256,14 +2281,14 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an ); let trigger = sqlx::query!( - "SELECT email, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", + "SELECT permissioned_as, edited_by FROM websocket_trigger WHERE path = $1 AND workspace_id = $2", "u/test-user-2/ws_nonadmin_update", "test-workspace" ) .fetch_one(&db) .await?; assert_eq!( - trigger.email, "test2@windmill.dev", + trigger.permissioned_as, "u/test-user-2", "Non-admin update should overwrite websocket trigger email with their own" ); assert_eq!( @@ -2273,3 +2298,513 @@ async fn test_websocket_trigger_update_preserves_email(db: Pool) -> an Ok(()) } + +/// Schedule: Admin sets permissioned_as to a group +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_schedule_group_permissioned_as(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // Create a script for the schedule + let resp = authed( + client().post(format!("{base}/scripts/create")), + "SECRET_TOKEN", + ) + .json(&new_script_with_on_behalf_of( + "u/test-user/scheduled_script_group", + None, + false, + )) + .send() + .await?; + assert_eq!( + resp.status(), + 201, + "Should create script: {}", + resp.text().await? + ); + + // Admin creates schedule with group-based permissioned_as + let resp = authed( + client().post(format!("{base}/schedules/create")), + "SECRET_TOKEN", + ) + .json(&json!({ + "path": "u/test-user/schedule_group_perm", + "schedule": "0 0 */6 * * *", + "timezone": "UTC", + "script_path": "u/test-user/scheduled_script_group", + "is_flow": false, + "enabled": false, + "permissioned_as": "g/all", + "preserve_permissioned_as": true + })) + .send() + .await?; + assert_eq!( + resp.status(), + 200, + "Admin should create schedule with group permissioned_as: {}", + resp.text().await? + ); + + let schedule = sqlx::query!( + "SELECT permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "u/test-user/schedule_group_perm", + "test-workspace" + ) + .fetch_one(&db) + .await?; + assert_eq!( + schedule.permissioned_as, "g/all", + "Admin should preserve group-based permissioned_as" + ); + assert_eq!( + schedule.edited_by, "test-user", + "edited_by should be the deploying user, not the group" + ); + + Ok(()) +} + +/// HTTP Trigger: Admin sets permissioned_as to a group +#[cfg(feature = "http_trigger")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_http_trigger_group_permissioned_as(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // Create a script first + let resp = authed( + client().post(format!("{base}/scripts/create")), + "SECRET_TOKEN", + ) + .json(&new_script_with_on_behalf_of( + "u/test-user/http_handler_group", + None, + false, + )) + .send() + .await?; + assert_eq!( + resp.status(), + 201, + "Should create script: {}", + resp.text().await? + ); + + let resp = authed( + client().post(format!("{base}/http_triggers/create")), + "SECRET_TOKEN", + ) + .json(&new_http_trigger( + "u/test-user/http_trigger_group_perm", + "u/test-user/http_handler_group", + "group-perm", + Some("g/all"), + true, + )) + .send() + .await?; + assert_eq!( + resp.status(), + 201, + "Admin should create trigger with group permissioned_as: {}", + resp.text().await? + ); + + let trigger = sqlx::query!( + "SELECT permissioned_as, edited_by FROM http_trigger WHERE path = $1 AND workspace_id = $2", + "u/test-user/http_trigger_group_perm", + "test-workspace" + ) + .fetch_one(&db) + .await?; + assert_eq!( + trigger.permissioned_as, "g/all", + "Admin should preserve group-based permissioned_as on trigger" + ); + assert_eq!( + trigger.edited_by, "test-user", + "edited_by should be the deploying user, not the group" + ); + + Ok(()) +} + +// ============================================================================ +// Schedule Create/Update Permission Tests (without preserve) +// ============================================================================ +// Verify that schedule create and update correctly set permissioned_as, email, +// edited_by on the schedule, and that the pushed job has correct created_by, +// permissioned_as, and permissioned_as_email fields. + +/// Helper to create a schedule and return the schedule + job fields +async fn create_schedule_and_get_job( + base: &str, + token: &str, + schedule_path: &str, + script_path: &str, + db: &Pool, +) -> anyhow::Result<( + // schedule fields + String, // email + String, // permissioned_as + String, // edited_by + // job fields + String, // created_by + String, // permissioned_as + String, // permissioned_as_email +)> { + let resp = authed(client().post(format!("{base}/schedules/create")), token) + .json(&json!({ + "path": schedule_path, + "schedule": "0 0 */6 * * *", + "timezone": "UTC", + "script_path": script_path, + "is_flow": false, + "enabled": true + })) + .send() + .await?; + assert_eq!( + resp.status(), + 200, + "Should create schedule: {}", + resp.text().await? + ); + + let schedule = sqlx::query!( + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + schedule_path, + "test-workspace" + ) + .fetch_one(db) + .await?; + + // Wait briefly for the job to be pushed + tokio::time::sleep(std::time::Duration::from_millis(200)).await; + + let job = sqlx::query!( + r#"SELECT created_by, permissioned_as, permissioned_as_email + FROM v2_job + WHERE workspace_id = 'test-workspace' + AND trigger_kind = 'schedule' + AND trigger = $1 + ORDER BY created_at DESC + LIMIT 1"#, + schedule_path + ) + .fetch_one(db) + .await?; + + Ok(( + schedule.email, + schedule.permissioned_as, + schedule.edited_by, + job.created_by, + job.permissioned_as, + job.permissioned_as_email, + )) +} + +/// Normal user creates a schedule — all fields should reflect that user +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_schedule_permissions_normal_user(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // Create script owned by the normal user + let resp = authed( + client().post(format!("{base}/scripts/create")), + "SECRET_TOKEN_2", + ) + .json(&new_script_with_on_behalf_of( + "u/test-user-2/sched_perm_script", + None, + false, + )) + .send() + .await?; + assert_eq!(resp.status(), 201, "create script: {}", resp.text().await?); + + let (sched_email, sched_pa, sched_edited_by, _job_created_by, job_pa, job_pa_email) = + create_schedule_and_get_job( + &base, + "SECRET_TOKEN_2", + "u/test-user-2/normal_user_schedule", + "u/test-user-2/sched_perm_script", + &db, + ) + .await?; + + assert_eq!(sched_email, "test2@windmill.dev", "schedule email"); + assert_eq!(sched_pa, "u/test-user-2", "schedule permissioned_as"); + assert_eq!(sched_edited_by, "test-user-2", "schedule edited_by"); + assert_eq!(job_pa, "u/test-user-2", "job permissioned_as"); + assert_eq!( + job_pa_email, "test2@windmill.dev", + "job permissioned_as_email" + ); + + // Now update the schedule (normal edit, no preserve) — fields should stay as the same user + let resp = authed( + client().post(format!( + "{base}/schedules/update/u/test-user-2/normal_user_schedule" + )), + "SECRET_TOKEN_2", + ) + .json(&json!({ + "schedule": "0 0 */12 * * *", + "timezone": "UTC" + })) + .send() + .await?; + assert_eq!( + resp.status(), + 200, + "update schedule: {}", + resp.text().await? + ); + + let schedule = sqlx::query!( + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "u/test-user-2/normal_user_schedule", + "test-workspace" + ) + .fetch_one(&db) + .await?; + assert_eq!(schedule.email, "test2@windmill.dev", "email after update"); + assert_eq!( + schedule.permissioned_as, "u/test-user-2", + "permissioned_as after update" + ); + assert_eq!(schedule.edited_by, "test-user-2", "edited_by after update"); + + Ok(()) +} + +/// Workspace admin creates a schedule — all fields should reflect the admin +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_schedule_permissions_workspace_admin(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // test-user is admin + superadmin in workspace + let resp = authed( + client().post(format!("{base}/scripts/create")), + "SECRET_TOKEN", + ) + .json(&new_script_with_on_behalf_of( + "u/test-user/admin_sched_script", + None, + false, + )) + .send() + .await?; + assert_eq!(resp.status(), 201, "create script: {}", resp.text().await?); + + let (sched_email, sched_pa, sched_edited_by, _job_created_by, job_pa, job_pa_email) = + create_schedule_and_get_job( + &base, + "SECRET_TOKEN", + "u/test-user/admin_schedule", + "u/test-user/admin_sched_script", + &db, + ) + .await?; + + assert_eq!(sched_email, "test@windmill.dev", "schedule email"); + assert_eq!(sched_pa, "u/test-user", "schedule permissioned_as"); + assert_eq!(sched_edited_by, "test-user", "schedule edited_by"); + assert_eq!(job_pa, "u/test-user", "job permissioned_as"); + assert_eq!( + job_pa_email, "test@windmill.dev", + "job permissioned_as_email" + ); + + // Admin edits a schedule owned by normal user — should take over ownership + let resp = authed( + client().post(format!("{base}/scripts/create")), + "SECRET_TOKEN_2", + ) + .json(&new_script_with_on_behalf_of( + "u/test-user-2/admin_edit_target_script", + None, + false, + )) + .send() + .await?; + assert_eq!(resp.status(), 201, "create script: {}", resp.text().await?); + + // Normal user creates it + let resp = authed( + client().post(format!("{base}/schedules/create")), + "SECRET_TOKEN_2", + ) + .json(&json!({ + "path": "u/test-user-2/admin_edit_target", + "schedule": "0 0 */6 * * *", + "timezone": "UTC", + "script_path": "u/test-user-2/admin_edit_target_script", + "is_flow": false, + "enabled": false + })) + .send() + .await?; + assert_eq!( + resp.status(), + 200, + "create schedule: {}", + resp.text().await? + ); + + // Admin edits it (no preserve) + let resp = authed( + client().post(format!( + "{base}/schedules/update/u/test-user-2/admin_edit_target" + )), + "SECRET_TOKEN", + ) + .json(&json!({ + "schedule": "0 0 */12 * * *", + "timezone": "UTC" + })) + .send() + .await?; + assert_eq!(resp.status(), 200, "admin update: {}", resp.text().await?); + + let schedule = sqlx::query!( + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "u/test-user-2/admin_edit_target", + "test-workspace" + ) + .fetch_one(&db) + .await?; + assert_eq!( + schedule.email, "test@windmill.dev", + "admin edit takes over email" + ); + assert_eq!( + schedule.permissioned_as, "u/test-user", + "admin edit takes over permissioned_as" + ); + assert_eq!(schedule.edited_by, "test-user", "admin edit sets edited_by"); + + Ok(()) +} + +/// Superadmin NOT in workspace creates a schedule — uses email as permissioned_as +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_schedule_permissions_superadmin_not_in_workspace( + db: Pool, +) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let base = format!("http://localhost:{port}/api/w/test-workspace"); + + // Superadmin not in workspace creates a script + let resp = authed( + client().post(format!("{base}/scripts/create")), + "EXTERNAL_SUPERADMIN_TOKEN", + ) + .json(&new_script_with_on_behalf_of( + "u/superadmin-external/sa_sched_script", + None, + false, + )) + .send() + .await?; + assert_eq!(resp.status(), 201, "create script: {}", resp.text().await?); + + // Superadmin creates a schedule + let resp = authed( + client().post(format!("{base}/schedules/create")), + "EXTERNAL_SUPERADMIN_TOKEN", + ) + .json(&json!({ + "path": "u/superadmin-external/sa_schedule", + "schedule": "0 0 */6 * * *", + "timezone": "UTC", + "script_path": "u/superadmin-external/sa_sched_script", + "is_flow": false, + "enabled": false + })) + .send() + .await?; + assert_eq!( + resp.status(), + 200, + "superadmin create schedule: {}", + resp.text().await? + ); + + let schedule = sqlx::query!( + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "u/superadmin-external/sa_schedule", + "test-workspace" + ) + .fetch_one(&db) + .await?; + + // Superadmin not in workspace: username_to_permissioned_as uses the email directly + // since the authed username for a superadmin not in workspace IS the email + assert_eq!( + schedule.email, "superadmin-external@windmill.dev", + "schedule email should be superadmin email" + ); + assert_eq!( + schedule.permissioned_as, + schedule.email.clone(), + "permissioned_as should match email for superadmin not in workspace" + ); + + // Update by the same superadmin + let resp = authed( + client().post(format!( + "{base}/schedules/update/u/superadmin-external/sa_schedule" + )), + "EXTERNAL_SUPERADMIN_TOKEN", + ) + .json(&json!({ + "schedule": "0 0 */12 * * *", + "timezone": "UTC" + })) + .send() + .await?; + assert_eq!( + resp.status(), + 200, + "superadmin update: {}", + resp.text().await? + ); + + let schedule_after = sqlx::query!( + "SELECT email, permissioned_as, edited_by FROM schedule WHERE path = $1 AND workspace_id = $2", + "u/superadmin-external/sa_schedule", + "test-workspace" + ) + .fetch_one(&db) + .await?; + assert_eq!( + schedule_after.permissioned_as, schedule.permissioned_as, + "permissioned_as should remain the same after self-edit" + ); + assert_eq!( + schedule_after.email, schedule.email, + "email should remain the same after self-edit" + ); + + Ok(()) +} diff --git a/backend/tests/trigger_listener_queries.rs b/backend/tests/trigger_listener_queries.rs new file mode 100644 index 0000000000..c169d5289a --- /dev/null +++ b/backend/tests/trigger_listener_queries.rs @@ -0,0 +1,385 @@ +//! Tests that call trigger trait methods directly to verify +//! all dynamic SQL correctly references the permissioned_as column. + +use serde_json::json; +use sqlx::{Pool, Postgres}; +use windmill_common::DB; +use windmill_trigger::handler::TriggerCrud; +use windmill_trigger::listener::Listener; +use windmill_trigger::types::TriggerMode; + +/// Helper to insert a minimal trigger row. +async fn insert_trigger(db: &DB, table: &str, path: &str, extra_cols: &str, extra_vals: &str) { + let sql = format!( + "INSERT INTO {} (path, script_path, is_flow, workspace_id, edited_by, permissioned_as{}) \ + VALUES ($1, 'f/test/handler', false, 'test-workspace', 'test-user', 'u/test-user'{})", + table, extra_cols, extra_vals + ); + sqlx::query(&sql).bind(path).execute(db).await.unwrap(); +} + +#[cfg(feature = "websocket")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_websocket(db: Pool) -> anyhow::Result<()> { + insert_trigger( + &db, + "websocket_trigger", + "f/test/listener_ws", + ", url", + ", 'wss://example.com'", + ) + .await; + let triggers = windmill_trigger_websocket::WebsocketTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_ws")); + Ok(()) +} + +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_postgres(db: Pool) -> anyhow::Result<()> { + insert_trigger( + &db, + "postgres_trigger", + "f/test/listener_pg", + ", postgres_resource_path, replication_slot_name, publication_name", + ", 'u/test/pg', 'slot', 'pub'", + ) + .await; + let triggers = windmill_trigger_postgres::PostgresTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_pg")); + Ok(()) +} + +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_mqtt(db: Pool) -> anyhow::Result<()> { + sqlx::query( + "INSERT INTO mqtt_trigger (path, mqtt_resource_path, subscribe_topics, client_version, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, false, 'test-workspace', 'test-user', 'u/test-user')" + ) + .bind("f/test/listener_mqtt").bind("u/test/mqtt") + .bind(json!({"topic": "t", "qos": "qos0"})).bind("v5").bind("f/test/handler") + .execute(&db).await?; + + let triggers = windmill_trigger_mqtt::MqttTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_mqtt")); + Ok(()) +} + +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_kafka(db: Pool) -> anyhow::Result<()> { + sqlx::query!( + "INSERT INTO kafka_trigger (path, kafka_resource_path, group_id, topics, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + "f/test/listener_kafka", + "u/test/kafka", + "grp", + &["topic"] as &[&str], + "f/test/handler" + ) + .execute(&db) + .await?; + + let triggers = windmill_trigger_kafka::KafkaTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_kafka")); + Ok(()) +} + +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_nats(db: Pool) -> anyhow::Result<()> { + sqlx::query!( + "INSERT INTO nats_trigger (path, nats_resource_path, subjects, use_jetstream, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + "f/test/listener_nats", + "u/test/nats", + &["subj"] as &[&str], + false, + "f/test/handler" + ) + .execute(&db) + .await?; + + let triggers = windmill_trigger_nats::NatsTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_nats")); + Ok(()) +} + +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_sqs(db: Pool) -> anyhow::Result<()> { + sqlx::query!( + "INSERT INTO sqs_trigger (path, queue_url, aws_resource_path, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, false, 'test-workspace', 'test-user', 'u/test-user')", + "f/test/listener_sqs", + "https://sqs.example.com/q", + "u/test/aws", + "f/test/handler" + ) + .execute(&db) + .await?; + + let triggers = windmill_trigger_sqs::SqsTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_sqs")); + Ok(()) +} + +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_listener_query_gcp(db: Pool) -> anyhow::Result<()> { + sqlx::query( + "INSERT INTO gcp_trigger (path, gcp_resource_path, topic_id, subscription_id, \ + delivery_type, subscription_mode, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, false, 'test-workspace', 'test-user', 'u/test-user')" + ) + .bind("f/test/listener_gcp").bind("u/test/gcp").bind("topic").bind("sub") + .bind("pull").bind("existing").bind("f/test/handler") + .execute(&db).await?; + + let triggers = windmill_trigger_gcp::GcpTrigger + .fetch_enabled_unlistened_triggers(&db) + .await?; + assert!(triggers.iter().any(|t| t.path == "f/test/listener_gcp")); + Ok(()) +} + +// ============================================================================ +// Handler trait method tests (get_trigger_by_path, list_triggers, set_trigger_mode) +// ============================================================================ + +fn make_authed() -> windmill_api_auth::ApiAuthed { + windmill_api_auth::ApiAuthed { + email: "test@windmill.dev".to_string(), + username: "test-user".to_string(), + is_admin: true, + is_operator: false, + groups: vec![], + folders: vec![], + scopes: None, + username_override: None, + token_prefix: None, + } +} + +/// Tests get_trigger_by_path, list_triggers, set_trigger_mode for websocket (server_state=true). +#[cfg(feature = "websocket")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_websocket(db: Pool) -> anyhow::Result<()> { + insert_trigger( + &db, + "websocket_trigger", + "f/test/handler_ws", + ", url", + ", 'wss://example.com'", + ) + .await; + + let handler = windmill_trigger_websocket::WebsocketTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_ws") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + let triggers = handler + .list_triggers(&mut *conn, "test-workspace", None) + .await?; + assert!(triggers.iter().any(|t| t.base.path == "f/test/handler_ws")); + + let authed = make_authed(); + let updated = handler + .set_trigger_mode( + &authed, + &mut *conn, + "test-workspace", + "f/test/handler_ws", + &TriggerMode::Disabled, + ) + .await?; + assert!(updated); + + Ok(()) +} + +/// Tests get_trigger_by_path, list_triggers, set_trigger_mode for mqtt (server_state=true). +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_mqtt(db: Pool) -> anyhow::Result<()> { + sqlx::query( + "INSERT INTO mqtt_trigger (path, mqtt_resource_path, subscribe_topics, client_version, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + ) + .bind("f/test/handler_mqtt").bind("u/test/mqtt") + .bind(json!({"topic": "t", "qos": "qos0"})).bind("v5").bind("f/test/handler") + .execute(&db).await?; + + let handler = windmill_trigger_mqtt::MqttTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_mqtt") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + let authed = make_authed(); + let updated = handler + .set_trigger_mode( + &authed, + &mut *conn, + "test-workspace", + "f/test/handler_mqtt", + &TriggerMode::Disabled, + ) + .await?; + assert!(updated); + + Ok(()) +} + +/// Tests handler queries for kafka trigger (EE only — OSS stub returns ()). +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_kafka(db: Pool) -> anyhow::Result<()> { + sqlx::query!( + "INSERT INTO kafka_trigger (path, kafka_resource_path, group_id, topics, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + "f/test/handler_kafka", + "u/test/kafka", + "grp", + &["topic"] as &[&str], + "f/test/handler" + ) + .execute(&db) + .await?; + + let handler = windmill_trigger_kafka::KafkaTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_kafka") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + Ok(()) +} + +/// Tests handler queries for postgres trigger. +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_postgres(db: Pool) -> anyhow::Result<()> { + insert_trigger( + &db, + "postgres_trigger", + "f/test/handler_pg", + ", postgres_resource_path, replication_slot_name, publication_name", + ", 'u/test/pg', 'slot', 'pub'", + ) + .await; + + let handler = windmill_trigger_postgres::PostgresTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_pg") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + Ok(()) +} + +/// Tests handler queries for nats trigger (EE only). +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_nats(db: Pool) -> anyhow::Result<()> { + sqlx::query!( + "INSERT INTO nats_trigger (path, nats_resource_path, subjects, use_jetstream, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, $5, false, 'test-workspace', 'test-user', 'u/test-user')", + "f/test/handler_nats", + "u/test/nats", + &["subj"] as &[&str], + false, + "f/test/handler" + ) + .execute(&db) + .await?; + + let handler = windmill_trigger_nats::NatsTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_nats") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + Ok(()) +} + +/// Tests handler queries for sqs trigger (EE only). +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_sqs(db: Pool) -> anyhow::Result<()> { + sqlx::query!( + "INSERT INTO sqs_trigger (path, queue_url, aws_resource_path, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, false, 'test-workspace', 'test-user', 'u/test-user')", + "f/test/handler_sqs", + "https://sqs.example.com/q", + "u/test/aws", + "f/test/handler" + ) + .execute(&db) + .await?; + + let handler = windmill_trigger_sqs::SqsTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_sqs") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + Ok(()) +} + +/// Tests handler queries for gcp trigger (EE only). +#[cfg(feature = "private")] +#[sqlx::test(fixtures("preserve_on_behalf_of"))] +async fn test_handler_queries_gcp(db: Pool) -> anyhow::Result<()> { + sqlx::query( + "INSERT INTO gcp_trigger (path, gcp_resource_path, topic_id, subscription_id, \ + delivery_type, subscription_mode, \ + script_path, is_flow, workspace_id, edited_by, permissioned_as) \ + VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, false, 'test-workspace', 'test-user', 'u/test-user')", + ) + .bind("f/test/handler_gcp").bind("u/test/gcp").bind("topic").bind("sub") + .bind("pull").bind("existing").bind("f/test/handler") + .execute(&db).await?; + + let handler = windmill_trigger_gcp::GcpTrigger; + let mut conn = db.acquire().await?; + + let trigger = handler + .get_trigger_by_path(&mut *conn, "test-workspace", "f/test/handler_gcp") + .await?; + assert_eq!(trigger.base.permissioned_as, "u/test-user"); + + Ok(()) +} diff --git a/backend/windmill-api-auth/src/auth.rs b/backend/windmill-api-auth/src/auth.rs index f3903fd6f4..8e3a4c7822 100644 --- a/backend/windmill-api-auth/src/auth.rs +++ b/backend/windmill-api-auth/src/auth.rs @@ -280,9 +280,7 @@ impl AuthCache { folders, scopes: None, username_override, - token_prefix: Some( - safe_token_prefix(token), - ), + token_prefix: Some(safe_token_prefix(token)), }) } else { let groups = vec![name.to_string()]; @@ -297,16 +295,17 @@ impl AuthCache { .unwrap_or_default(); Some(ApiAuthed { email: email, - username: format!("group-{name}"), + username: format!( + "{}{name}", + windmill_common::users::USERNAME_GROUP_PREFIX + ), is_admin: false, groups, is_operator: false, folders, scopes: None, username_override, - token_prefix: Some( - safe_token_prefix(token), - ), + token_prefix: Some(safe_token_prefix(token)), }) } } else { @@ -369,9 +368,7 @@ impl AuthCache { folders, scopes, username_override, - token_prefix: Some( - safe_token_prefix(token), - ), + token_prefix: Some(safe_token_prefix(token)), }) } None if super_admin => Some(ApiAuthed { @@ -383,9 +380,7 @@ impl AuthCache { folders: vec![], scopes, username_override, - token_prefix: Some( - safe_token_prefix(token), - ), + token_prefix: Some(safe_token_prefix(token)), }), None => None, } diff --git a/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql b/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql index 66f406c08c..343fa81a30 100644 --- a/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql +++ b/backend/windmill-api-integration-tests/tests/fixtures/permissions_test.sql @@ -204,14 +204,14 @@ VALUES ('test-workspace', 'u/bob/my_variable', 'bob_value', false, -- ============================================ -- Alice's personal schedule -INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms) +INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as) VALUES ('test-workspace', 'u/alice/my_schedule', 'alice', NOW(), '0 * * * *', false, - 'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}'); + 'u/alice/my_script', '{}', false, 'alice@windmill.dev', 'UTC', '{}', 'u/alice'); -- Bob's personal schedule -INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms) +INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, enabled, script_path, args, is_flow, email, timezone, extra_perms, permissioned_as) VALUES ('test-workspace', 'u/bob/my_schedule', 'bob', NOW(), '0 * * * *', false, - 'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}'); + 'u/bob/my_script', '{}', false, 'bob@windmill.dev', 'UTC', '{}', 'u/bob'); -- ============================================ -- APPS (with app_versions) diff --git a/backend/windmill-api-integration-tests/tests/trigger_e2e.rs b/backend/windmill-api-integration-tests/tests/trigger_e2e.rs index 1d4f122bcc..a647692579 100644 --- a/backend/windmill-api-integration-tests/tests/trigger_e2e.rs +++ b/backend/windmill-api-integration-tests/tests/trigger_e2e.rs @@ -158,7 +158,7 @@ async fn test_mqtt_e2e(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO mqtt_trigger ( path, mqtt_resource_path, subscribe_topics, client_version, - script_path, is_flow, workspace_id, edited_by, email + script_path, is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9) "#, @@ -171,7 +171,7 @@ async fn test_mqtt_e2e(db: Pool) -> anyhow::Result<()> { .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -240,7 +240,7 @@ async fn test_websocket_e2e(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO websocket_trigger ( path, url, script_path, is_flow, workspace_id, - edited_by, email, initial_messages + edited_by, permissioned_as, initial_messages ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, @@ -250,7 +250,7 @@ async fn test_websocket_e2e(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", &[json!({"type": "RawMessage", "content": "hello from e2e test"})] as &[serde_json::Value], ) @@ -338,7 +338,7 @@ async fn test_postgres_e2e(db: Pool) -> anyhow::Result<()> { sqlx::query( r#" INSERT INTO postgres_trigger ( - path, script_path, is_flow, workspace_id, edited_by, email, + path, script_path, is_flow, workspace_id, edited_by, permissioned_as, postgres_resource_path, replication_slot_name, publication_name ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) @@ -349,7 +349,7 @@ async fn test_postgres_e2e(db: Pool) -> anyhow::Result<()> { .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .bind("u/test-user/pg_res") .bind(&slot_name) .bind(&pub_name) @@ -410,7 +410,7 @@ async fn test_kafka_e2e(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO kafka_trigger ( path, kafka_resource_path, topics, group_id, - script_path, is_flow, workspace_id, edited_by, email + script_path, is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, @@ -422,7 +422,7 @@ async fn test_kafka_e2e(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -502,7 +502,7 @@ async fn test_nats_e2e(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO nats_trigger ( path, nats_resource_path, subjects, script_path, - is_flow, workspace_id, edited_by, email, use_jetstream + is_flow, workspace_id, edited_by, permissioned_as, use_jetstream ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, @@ -513,7 +513,7 @@ async fn test_nats_e2e(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", false, ) .execute(&db) @@ -580,7 +580,7 @@ async fn test_sqs_e2e(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO sqs_trigger ( path, queue_url, aws_resource_path, script_path, - is_flow, workspace_id, edited_by, email + is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, @@ -591,7 +591,7 @@ async fn test_sqs_e2e(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -675,7 +675,7 @@ async fn test_gcp_e2e(db: Pool) -> anyhow::Result<()> { INSERT INTO gcp_trigger ( path, gcp_resource_path, topic_id, subscription_id, delivery_type, subscription_mode, script_path, is_flow, - workspace_id, edited_by, email + workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, $8, $9, $10, $11) "#, @@ -690,7 +690,7 @@ async fn test_gcp_e2e(db: Pool) -> anyhow::Result<()> { .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; diff --git a/backend/windmill-api-integration-tests/tests/triggers.rs b/backend/windmill-api-integration-tests/tests/triggers.rs index b55f24397c..980bdc571f 100644 --- a/backend/windmill-api-integration-tests/tests/triggers.rs +++ b/backend/windmill-api-integration-tests/tests/triggers.rs @@ -557,7 +557,7 @@ async fn test_http_trigger_insert_and_query(db: Pool) -> anyhow::Resul r#" INSERT INTO http_trigger ( path, route_path, route_path_key, script_path, is_flow, - workspace_id, edited_by, email, http_method, + workspace_id, edited_by, permissioned_as, http_method, authentication_method, is_static_website, workspaced_route, wrap_body, raw_string ) @@ -571,7 +571,7 @@ async fn test_http_trigger_insert_and_query(db: Pool) -> anyhow::Resul false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "post" as _, "none" as _, false, @@ -615,7 +615,7 @@ async fn test_http_trigger_multiple_methods(db: Pool) -> anyhow::Resul r#" INSERT INTO http_trigger ( path, route_path, route_path_key, script_path, is_flow, - workspace_id, edited_by, email, http_method, + workspace_id, edited_by, permissioned_as, http_method, authentication_method, is_static_website, workspaced_route, wrap_body, raw_string ) @@ -629,7 +629,7 @@ async fn test_http_trigger_multiple_methods(db: Pool) -> anyhow::Resul false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", *method as _, "none" as _, false, @@ -662,7 +662,7 @@ async fn test_http_trigger_authentication_methods(db: Pool) -> anyhow: r#" INSERT INTO http_trigger ( path, route_path, route_path_key, script_path, is_flow, - workspace_id, edited_by, email, http_method, + workspace_id, edited_by, permissioned_as, http_method, authentication_method, is_static_website, workspaced_route, wrap_body, raw_string ) @@ -676,7 +676,7 @@ async fn test_http_trigger_authentication_methods(db: Pool) -> anyhow: false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "get" as _, *auth as _, false, @@ -710,7 +710,7 @@ async fn test_http_trigger_update(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO http_trigger ( path, route_path, route_path_key, script_path, is_flow, - workspace_id, edited_by, email, http_method, + workspace_id, edited_by, permissioned_as, http_method, authentication_method, is_static_website, workspaced_route, wrap_body, raw_string ) @@ -724,7 +724,7 @@ async fn test_http_trigger_update(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "get" as _, "none" as _, false, @@ -763,7 +763,7 @@ async fn test_http_trigger_delete(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO http_trigger ( path, route_path, route_path_key, script_path, is_flow, - workspace_id, edited_by, email, http_method, + workspace_id, edited_by, permissioned_as, http_method, authentication_method, is_static_website, workspaced_route, wrap_body, raw_string ) @@ -777,7 +777,7 @@ async fn test_http_trigger_delete(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "get" as _, "none" as _, false, @@ -818,7 +818,7 @@ async fn test_http_trigger_mode_filtering(db: Pool) -> anyhow::Result< r#" INSERT INTO http_trigger ( path, route_path, route_path_key, script_path, is_flow, - workspace_id, edited_by, email, http_method, + workspace_id, edited_by, permissioned_as, http_method, authentication_method, is_static_website, workspaced_route, wrap_body, raw_string, mode ) @@ -832,7 +832,7 @@ async fn test_http_trigger_mode_filtering(db: Pool) -> anyhow::Result< false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "get" as _, "none" as _, false, @@ -872,7 +872,7 @@ async fn test_websocket_trigger_insert(db: Pool) -> anyhow::Result<()> r#" INSERT INTO websocket_trigger ( path, url, script_path, is_flow, workspace_id, - edited_by, email + edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7) "#, @@ -882,7 +882,7 @@ async fn test_websocket_trigger_insert(db: Pool) -> anyhow::Result<()> false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -911,7 +911,7 @@ async fn test_kafka_trigger_insert(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO kafka_trigger ( path, kafka_resource_path, topics, group_id, script_path, - is_flow, workspace_id, edited_by, email + is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, @@ -923,7 +923,7 @@ async fn test_kafka_trigger_insert(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -958,7 +958,7 @@ async fn test_kafka_trigger_insert_auto_commit_disabled(db: Pool) -> a r#" INSERT INTO kafka_trigger ( path, kafka_resource_path, topics, group_id, script_path, - is_flow, workspace_id, edited_by, email, auto_commit + is_flow, workspace_id, edited_by, permissioned_as, auto_commit ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) "#, @@ -970,7 +970,7 @@ async fn test_kafka_trigger_insert_auto_commit_disabled(db: Pool) -> a false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", false, ) .execute(&db) @@ -998,7 +998,7 @@ async fn test_postgres_trigger_insert(db: Pool) -> anyhow::Result<()> sqlx::query!( r#" INSERT INTO postgres_trigger ( - path, script_path, is_flow, workspace_id, edited_by, email, + path, script_path, is_flow, workspace_id, edited_by, permissioned_as, postgres_resource_path, replication_slot_name, publication_name ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) @@ -1008,7 +1008,7 @@ async fn test_postgres_trigger_insert(db: Pool) -> anyhow::Result<()> false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "u/admin/pg_resource", "test_slot", "test_publication", @@ -1042,7 +1042,7 @@ async fn test_nats_trigger_insert(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO nats_trigger ( path, nats_resource_path, subjects, script_path, - is_flow, workspace_id, edited_by, email, use_jetstream + is_flow, workspace_id, edited_by, permissioned_as, use_jetstream ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, @@ -1053,7 +1053,7 @@ async fn test_nats_trigger_insert(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", false, ) .execute(&db) @@ -1085,7 +1085,7 @@ async fn test_sqs_trigger_insert(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO sqs_trigger ( path, queue_url, aws_resource_path, script_path, - is_flow, workspace_id, edited_by, email + is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, @@ -1096,7 +1096,7 @@ async fn test_sqs_trigger_insert(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -1136,7 +1136,7 @@ async fn test_trigger_server_state_tracking(db: Pool) -> anyhow::Resul r#" INSERT INTO websocket_trigger ( path, url, script_path, is_flow, workspace_id, - edited_by, email, server_id, error + edited_by, permissioned_as, server_id, error ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) "#, @@ -1146,7 +1146,7 @@ async fn test_trigger_server_state_tracking(db: Pool) -> anyhow::Resul false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", "server-abc-123", "connection refused", ) @@ -1194,7 +1194,7 @@ async fn test_trigger_mode_filtering(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO websocket_trigger ( path, url, script_path, is_flow, workspace_id, - edited_by, email, mode + edited_by, permissioned_as, mode ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8::trigger_mode) "#, @@ -1204,7 +1204,7 @@ async fn test_trigger_mode_filtering(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", *mode as _, ) .execute(&db) @@ -1276,9 +1276,9 @@ async fn test_schedule_insert_and_query(db: Pool) -> anyhow::Result<() r#" INSERT INTO schedule ( workspace_id, path, edited_by, schedule, enabled, - script_path, is_flow, email, timezone + script_path, is_flow, email, timezone, permissioned_as ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) "#, "test-workspace", "f/test/my_schedule", @@ -1289,6 +1289,7 @@ async fn test_schedule_insert_and_query(db: Pool) -> anyhow::Result<() false, "test@windmill.dev", "UTC", + "u/test-user", ) .execute(&db) .await?; @@ -1324,7 +1325,7 @@ async fn test_mqtt_trigger_insert(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO mqtt_trigger ( path, mqtt_resource_path, subscribe_topics, client_version, - script_path, is_flow, workspace_id, edited_by, email + script_path, is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9) "#, @@ -1337,7 +1338,7 @@ async fn test_mqtt_trigger_insert(db: Pool) -> anyhow::Result<()> { .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -1368,7 +1369,7 @@ async fn test_mqtt_trigger_update(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO mqtt_trigger ( path, mqtt_resource_path, subscribe_topics, client_version, - script_path, is_flow, workspace_id, edited_by, email + script_path, is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9) "#, @@ -1381,7 +1382,7 @@ async fn test_mqtt_trigger_update(db: Pool) -> anyhow::Result<()> { .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -1413,7 +1414,7 @@ async fn test_mqtt_trigger_delete(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO mqtt_trigger ( path, mqtt_resource_path, subscribe_topics, client_version, - script_path, is_flow, workspace_id, edited_by, email + script_path, is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, ARRAY[$3::jsonb], $4::mqtt_client_version, $5, $6, $7, $8, $9) "#, @@ -1426,7 +1427,7 @@ async fn test_mqtt_trigger_delete(db: Pool) -> anyhow::Result<()> { .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -1462,7 +1463,7 @@ async fn test_gcp_trigger_insert_pull(db: Pool) -> anyhow::Result<()> INSERT INTO gcp_trigger ( path, gcp_resource_path, topic_id, subscription_id, delivery_type, subscription_mode, script_path, is_flow, - workspace_id, edited_by, email + workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, $8, $9, $10, $11) "#, @@ -1477,7 +1478,7 @@ async fn test_gcp_trigger_insert_pull(db: Pool) -> anyhow::Result<()> .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -1513,7 +1514,7 @@ async fn test_gcp_trigger_insert_push(db: Pool) -> anyhow::Result<()> INSERT INTO gcp_trigger ( path, gcp_resource_path, topic_id, subscription_id, delivery_type, delivery_config, subscription_mode, - script_path, is_flow, workspace_id, edited_by, email + script_path, is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::jsonb, $7::gcp_subscription_mode, $8, $9, $10, $11, $12) "#, @@ -1529,7 +1530,7 @@ async fn test_gcp_trigger_insert_push(db: Pool) -> anyhow::Result<()> .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -1562,7 +1563,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool) -> anyhow::Resul INSERT INTO gcp_trigger ( path, gcp_resource_path, topic_id, subscription_id, delivery_type, subscription_mode, script_path, is_flow, - workspace_id, edited_by, email + workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5::delivery_mode, $6::gcp_subscription_mode, $7, $8, $9, $10, $11) "#; @@ -1578,7 +1579,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool) -> anyhow::Resul .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await?; @@ -1594,7 +1595,7 @@ async fn test_gcp_trigger_unique_constraint(db: Pool) -> anyhow::Resul .bind(false) .bind("test-workspace") .bind("test-user") - .bind("test@windmill.dev") + .bind("u/test-user") .execute(&db) .await; @@ -1616,7 +1617,7 @@ async fn test_email_trigger_insert(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO email_trigger ( path, local_part, workspaced_local_part, script_path, - is_flow, workspace_id, edited_by, email + is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, @@ -1627,7 +1628,7 @@ async fn test_email_trigger_insert(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -1659,7 +1660,7 @@ async fn test_email_trigger_update(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO email_trigger ( path, local_part, workspaced_local_part, script_path, - is_flow, workspace_id, edited_by, email + is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, @@ -1670,7 +1671,7 @@ async fn test_email_trigger_update(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; @@ -1705,7 +1706,7 @@ async fn test_email_trigger_delete(db: Pool) -> anyhow::Result<()> { r#" INSERT INTO email_trigger ( path, local_part, workspaced_local_part, script_path, - is_flow, workspace_id, edited_by, email + is_flow, workspace_id, edited_by, permissioned_as ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, @@ -1716,7 +1717,7 @@ async fn test_email_trigger_delete(db: Pool) -> anyhow::Result<()> { false, "test-workspace", "test-user", - "test@windmill.dev", + "u/test-user", ) .execute(&db) .await?; diff --git a/backend/windmill-api-schedule/src/lib.rs b/backend/windmill-api-schedule/src/lib.rs index 7a82523ff9..a6224a7639 100644 --- a/backend/windmill-api-schedule/src/lib.rs +++ b/backend/windmill-api-schedule/src/lib.rs @@ -33,42 +33,22 @@ use windmill_common::{ use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; use windmill_queue::schedule::push_scheduled_job; -/// Resolves the email to use for a schedule based on preservation settings. -/// When preserving, looks up the email from the provided username. -async fn resolve_email( - username: Option<&String>, - preserve_email: Option, - authed: &ApiAuthed, - db: &DB, - w_id: &str, -) -> Result { - if let Some(username) = username { - if preserve_email.unwrap_or(false) && can_preserve_on_behalf_of(authed) { - let email = sqlx::query_scalar!( - "SELECT email FROM usr WHERE username = $1 AND workspace_id = $2", - username, - w_id - ) - .fetch_optional(db) - .await?; - if let Some(email) = email { - return Ok(email); - } - } - } - Ok(authed.email.clone()) -} - -fn resolve_edited_by( - username: Option<&String>, - preserve_edited_by: Option, +/// Resolves the permissioned_as value for a schedule. +/// When preserving, uses the provided permissioned_as value directly. +fn resolve_permissioned_as( + permissioned_as: Option<&String>, + preserve_permissioned_as: Option, authed: &ApiAuthed, ) -> String { - if let Some(username) = username { - if preserve_edited_by.unwrap_or(false) && can_preserve_on_behalf_of(authed) { - return username.clone(); + if let Some(permissioned_as) = permissioned_as { + if preserve_permissioned_as.unwrap_or(false) && can_preserve_on_behalf_of(authed) { + return permissioned_as.clone(); } } + windmill_common::users::username_to_permissioned_as(&authed.username) +} + +fn resolve_edited_by(authed: &ApiAuthed) -> String { authed.username.clone() } @@ -117,8 +97,8 @@ pub struct NewSchedule { pub paused_until: Option>, pub cron_version: Option, pub dynamic_skip: Option, - pub email: Option, - pub preserve_email: Option, + pub permissioned_as: Option, + pub preserve_permissioned_as: Option, } #[derive(Serialize, Deserialize)] @@ -244,14 +224,26 @@ async fn create_schedule( validate_dynamic_skip(&mut tx, &w_id, handler_path).await?; } - let resolved_edited_by = resolve_edited_by(ns.email.as_ref(), ns.preserve_email, &authed); + let resolved_edited_by = resolve_edited_by(&authed); + let resolved_permissioned_as = resolve_permissioned_as( + ns.permissioned_as.as_ref(), + ns.preserve_permissioned_as, + &authed, + ); + // email is still written for backwards compat with old workers that don't know about permissioned_as + let resolved_email = windmill_common::users::get_email_from_permissioned_as( + &resolved_permissioned_as, + &w_id, + &db, + ) + .await?; let schedule = sqlx::query_as!( Schedule, r#" INSERT INTO schedule ( workspace_id, path, schedule, timezone, edited_by, script_path, - is_flow, args, enabled, email, + is_flow, args, enabled, email, permissioned_as, on_failure, on_failure_times, on_failure_exact, on_failure_extra_args, on_recovery, on_recovery_times, on_recovery_extra_args, on_success, on_success_extra_args, @@ -259,12 +251,12 @@ async fn create_schedule( tag, paused_until, cron_version, description, dynamic_skip ) VALUES ( $1, $2, $3, $4, $5, $6, - $7, $8, $9, $10, - $11, $12, $13, $14, - $15, $16, $17, - $18, $19, - $20, $21, $22, $23, - $24, $25, $26, $27, $28 + $7, $8, $9, $10, $11, + $12, $13, $14, $15, + $16, $17, $18, + $19, $20, + $21, $22, $23, $24, + $25, $26, $27, $28, $29 ) RETURNING workspace_id, @@ -279,6 +271,7 @@ async fn create_schedule( args AS "args: _", extra_perms, email, + permissioned_as, error, on_failure, on_failure_times, @@ -309,7 +302,8 @@ async fn create_schedule( to_json_raw_opt(ns.args.as_ref()) as Option>>, ns.enabled.unwrap_or(false), - resolve_email(ns.email.as_ref(), ns.preserve_email, &authed, &db, &w_id).await?, + resolved_email, + resolved_permissioned_as, ns.on_failure, ns.on_failure_times, ns.on_failure_exact, @@ -355,8 +349,8 @@ async fn create_schedule( ) .await?; if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation( - ns.email.as_deref(), - ns.preserve_email.unwrap_or(false), + ns.permissioned_as.as_deref(), + ns.preserve_permissioned_as.unwrap_or(false), &authed, &authed.username, ) { @@ -421,7 +415,28 @@ async fn edit_schedule( clear_schedule(&mut tx, path, &w_id).await?; - let resolved_edited_by = resolve_edited_by(es.email.as_ref(), es.preserve_email, &authed); + let resolved_edited_by = resolve_edited_by(&authed); + + let resolved_permissioned_as = resolve_permissioned_as( + es.permissioned_as.as_ref(), + es.preserve_permissioned_as, + &authed, + ); + + // email is still written for backwards compat with old workers that don't know about permissioned_as. + // When permissioned_as is preserved to a different user, derive email from it. + let resolved_email = if resolved_permissioned_as + != windmill_common::users::username_to_permissioned_as(&authed.username) + { + windmill_common::users::get_email_from_permissioned_as( + &resolved_permissioned_as, + &w_id, + &db, + ) + .await? + } else { + authed.email.clone() + }; let schedule = sqlx::query_as!( Schedule, @@ -450,8 +465,9 @@ async fn edit_schedule( cron_version = COALESCE($21, cron_version), description = $22, dynamic_skip = $23, - email = COALESCE($24, email), - edited_by = $25 + email = $24, + edited_by = $25, + permissioned_as = $26 WHERE path = $19 AND workspace_id = $20 RETURNING workspace_id, @@ -466,6 +482,7 @@ async fn edit_schedule( args AS "args: _", extra_perms, email, + permissioned_as, error, on_failure, on_failure_times, @@ -513,8 +530,9 @@ async fn edit_schedule( es.cron_version, es.description, es.dynamic_skip, - Some(resolve_email(es.email.as_ref(), es.preserve_email, &authed, &db, &w_id).await?), - resolved_edited_by + resolved_email, + resolved_edited_by, + resolved_permissioned_as ) .fetch_one(&mut *tx) .await @@ -535,9 +553,10 @@ async fn edit_schedule( ), ) .await?; + if let Some(on_behalf_of) = windmill_common::check_on_behalf_of_preservation( - es.email.as_deref(), - es.preserve_email.unwrap_or(false), + es.permissioned_as.as_deref(), + es.preserve_permissioned_as.unwrap_or(false), &authed, &authed.username, ) { @@ -547,14 +566,8 @@ async fn edit_schedule( "schedule.on_behalf_of", ActionKind::Update, &w_id, - Some(path), - Some( - [ - ("on_behalf_of", on_behalf_of.as_str()), - ("action", "update"), - ] - .into(), - ), + Some(&path.to_string()), + Some([("on_behalf_of", on_behalf_of.as_str()), ("action", "edit")].into()), ) .await?; } @@ -788,6 +801,7 @@ pub async fn set_enabled( let mut tx = user_db.begin(&authed).await?; let path = path.to_path(); check_scopes(&authed, || format!("schedules:write:{}", path))?; + // email is still written for backwards compat with old workers that don't know about permissioned_as let schedule_o = sqlx::query_as!( Schedule, r#" @@ -808,6 +822,7 @@ pub async fn set_enabled( args AS "args: _", extra_perms, email, + permissioned_as, error, on_failure, on_failure_times, @@ -1179,8 +1194,8 @@ pub struct EditSchedule { pub paused_until: Option>, pub cron_version: Option, pub dynamic_skip: Option, - pub email: Option, - pub preserve_email: Option, + pub permissioned_as: Option, + pub preserve_permissioned_as: Option, } pub use windmill_queue::schedule::clear_schedule; diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 0a64da3827..ebe1d2d618 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -20483,6 +20483,9 @@ components: email: type: string description: Email of the user who owns this schedule, used for permissioned_as + permissioned_as: + type: string + description: The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup') error: type: string nullable: true @@ -20565,6 +20568,7 @@ components: - is_flow - enabled - email + - permissioned_as ScheduleWJobs: allOf: @@ -20686,12 +20690,12 @@ components: type: string nullable: true description: Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false) - email: + permissioned_as: type: string - description: Email of the user who the scheduled jobs run as. Used during deployment to preserve the original schedule owner. - preserve_email: + description: The user or group this schedule runs as. Used during deployment to preserve the original schedule owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - schedule @@ -20779,12 +20783,14 @@ components: type: string nullable: true description: Path to a script that validates scheduled datetimes. Receives scheduled_for datetime and returns boolean to skip (true) or run (false) - email: + permissioned_as: type: string - description: Email of the user who the scheduled jobs run as. Used during deployment to preserve the original schedule owner. - preserve_email: + nullable: true + description: "The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup'). Only admins and wm_deployers can set this via preserve_permissioned_as." + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + nullable: true + description: "If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using the deploying user's identity" required: - schedule - timezone @@ -20825,9 +20831,9 @@ components: script_path: type: string description: Path to the script or flow to execute when triggered - email: + permissioned_as: type: string - description: Email of the user who owns this trigger, used for permissioned_as + description: The user or group this trigger runs as (permissioned_as) extra_perms: type: object description: Additional permissions for this trigger @@ -20852,7 +20858,7 @@ components: required: - path - script_path - - email + - permissioned_as - extra_perms - workspace_id - edited_by @@ -21141,12 +21147,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -21233,12 +21239,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -21401,12 +21407,12 @@ components: retry: description: Retry configuration for failed executions $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -21469,12 +21475,12 @@ components: retry: description: Retry configuration for failed executions $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -21644,12 +21650,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -21704,12 +21710,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -21846,12 +21852,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: "Retry configuration for failed executions." - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -22014,12 +22020,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - queue_url - aws_resource_path @@ -22066,12 +22072,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - queue_url - aws_resource_path @@ -22230,12 +22236,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -22278,12 +22284,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -22413,12 +22419,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -22483,12 +22489,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -22590,12 +22596,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -22645,12 +22651,12 @@ components: retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" description: Retry configuration for failed executions - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path @@ -22699,12 +22705,12 @@ components: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" mode: $ref: "#/components/schemas/TriggerMode" - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path @@ -22731,12 +22737,12 @@ components: $ref: "#/components/schemas/ScriptArgs" retry: $ref: "../../openflow.openapi.yaml#/components/schemas/Retry" - email: + permissioned_as: type: string - description: Email of the user who triggered jobs run as. Used during deployment to preserve the original trigger owner. - preserve_email: + description: The user or group this trigger runs as. Used during deployment to preserve the original trigger owner. + preserve_permissioned_as: type: boolean - description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original email value instead of overwriting it." + description: "When true and the caller is a member of the 'wm_deployers' group, preserves the original permissioned_as value instead of overwriting it." required: - path - script_path diff --git a/backend/windmill-api/src/triggers/http/handler.rs b/backend/windmill-api/src/triggers/http/handler.rs index fcb883f089..8af10bda18 100644 --- a/backend/windmill-api/src/triggers/http/handler.rs +++ b/backend/windmill-api/src/triggers/http/handler.rs @@ -10,7 +10,6 @@ use crate::{ get_runnable_format, trigger_runnable, trigger_runnable_and_wait_for_result, trigger_runnable_inner, RunnableId, }, - users::fetch_api_authed, utils::{check_scopes, ExpiringCacheEntry}, }; use axum::{ @@ -214,9 +213,15 @@ async fn get_http_route_trigger( None }; - let authed = fetch_api_authed( - trigger.edited_by.clone(), - trigger.email.clone(), + let email = windmill_common::users::get_email_from_permissioned_as( + &trigger.permissioned_as, + &trigger.workspace_id, + &db, + ) + .await?; + let authed = windmill_api_auth::fetch_api_authed_from_permissioned_as( + trigger.permissioned_as.clone(), + email, &trigger.workspace_id, &db, Some(username_override.unwrap_or(format!("HTTP-{}", trigger.path))), diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index bccd3736ab..d685d4c4cc 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -210,6 +210,7 @@ where "updated_by", "edited_at", "edited_by", + "permissioned_as", "archived", "has_draft", "error", diff --git a/backend/windmill-common/Cargo.toml b/backend/windmill-common/Cargo.toml index 636061571e..a0aa27342d 100644 --- a/backend/windmill-common/Cargo.toml +++ b/backend/windmill-common/Cargo.toml @@ -124,6 +124,7 @@ opentelemetry = { workspace = true, optional = true } tracing-opentelemetry = { workspace = true, optional = true } opentelemetry-appender-tracing = { workspace = true, optional = true } tonic = { workspace = true, optional = true } +equivalent = "1.0.2" [target.'cfg(not(target_env = "msvc"))'.dependencies] tikv-jemalloc-ctl = { optional = true, workspace = true } diff --git a/backend/windmill-common/src/auth.rs b/backend/windmill-common/src/auth.rs index 23604b6e3a..9872950ffb 100644 --- a/backend/windmill-common/src/auth.rs +++ b/backend/windmill-common/src/auth.rs @@ -35,10 +35,7 @@ pub const TOKEN_PREFIX_LEN: usize = 10; /// Safely extract the token prefix (first TOKEN_PREFIX_LEN chars). /// Returns the full token if it's shorter than TOKEN_PREFIX_LEN, preventing panics. pub fn safe_token_prefix(token: &str) -> String { - token - .get(..TOKEN_PREFIX_LEN) - .unwrap_or(token) - .to_string() + token.get(..TOKEN_PREFIX_LEN).unwrap_or(token).to_string() } lazy_static::lazy_static! { @@ -121,10 +118,11 @@ pub fn check_extra_perms( groups: &[String], ) -> Option { // Check direct user permission - let user_key = if username.starts_with("u/") { + use crate::users::{PERMISSIONED_AS_GROUP_PREFIX, PERMISSIONED_AS_USER_PREFIX}; + let user_key = if username.starts_with(PERMISSIONED_AS_USER_PREFIX) { username.to_string() } else { - format!("u/{username}") + format!("{PERMISSIONED_AS_USER_PREFIX}{username}") }; if let Some(v) = extra_perms.get(&user_key) { return Some(v.as_bool().unwrap_or(false)); @@ -134,10 +132,10 @@ pub fn check_extra_perms( let mut found = false; let mut write = false; for g in groups { - let key = if g.starts_with("g/") { + let key = if g.starts_with(PERMISSIONED_AS_GROUP_PREFIX) { g.to_string() } else { - format!("g/{g}") + format!("{PERMISSIONED_AS_GROUP_PREFIX}{g}") }; if let Some(v) = extra_perms.get(&key) { found = true; @@ -293,12 +291,13 @@ pub async fn is_devops_email(db: &DB, email: &str) -> Result { } pub fn permissioned_as_to_username(permissioned_as: &str) -> String { - if let Some((prefix, name)) = permissioned_as.split_once('/') { - if prefix == "u" { - name.to_string() - } else { - format!("group-{}", name) - } + use crate::users::{PERMISSIONED_AS_USER_PREFIX, USERNAME_GROUP_PREFIX}; + if let Some(name) = permissioned_as.strip_prefix(PERMISSIONED_AS_USER_PREFIX) { + name.to_string() + } else if let Some(name) = + permissioned_as.strip_prefix(crate::users::PERMISSIONED_AS_GROUP_PREFIX) + { + format!("{}{}", USERNAME_GROUP_PREFIX, name) } else { permissioned_as.to_string() } @@ -379,7 +378,7 @@ async fn fetch_authed_from_permissioned_as_inner( let folders = get_folders_for_user(&w_id, "", &groups, &mut *conn).await?; Ok(Authed { email: email.to_string(), - username: format!("group-{name}"), + username: format!("{}{name}", crate::users::USERNAME_GROUP_PREFIX), is_admin: false, groups, is_operator: false, diff --git a/backend/windmill-common/src/users.rs b/backend/windmill-common/src/users.rs index 288337eb40..453466ac7d 100644 --- a/backend/windmill-common/src/users.rs +++ b/backend/windmill-common/src/users.rs @@ -12,11 +12,72 @@ pub const SUPERADMIN_SYNC_EMAIL: &str = "superadmin_sync@windmill.dev"; pub const COOKIE_NAME: &str = "token"; +/// Prefix for user-based permissioned_as values: "u/" +pub const PERMISSIONED_AS_USER_PREFIX: &str = "u/"; +/// Prefix for group-based permissioned_as values: "g/" +pub const PERMISSIONED_AS_GROUP_PREFIX: &str = "g/"; +/// Prefix for group-based usernames: "group-" +pub const USERNAME_GROUP_PREFIX: &str = "group-"; + pub fn username_to_permissioned_as(user: &str) -> String { if user.contains('@') { user.to_string() + } else if let Some(group) = user.strip_prefix(USERNAME_GROUP_PREFIX) { + format!("{}{}", PERMISSIONED_AS_GROUP_PREFIX, group) } else { - format!("u/{}", user) + format!("{}{}", PERMISSIONED_AS_USER_PREFIX, user) + } +} + +/// Borrowed key for zero-allocation cache lookups via `Equivalent<(String, String)>`. +#[derive(Hash)] +struct EmailCacheKey<'a>(&'a str, &'a str); + +impl equivalent::Equivalent<(String, String)> for EmailCacheKey<'_> { + fn equivalent(&self, key: &(String, String)) -> bool { + self.0 == key.0 && self.1 == key.1 + } +} + +lazy_static::lazy_static! { + static ref EMAIL_CACHE: quick_cache::sync::Cache<(String, String), (String, std::time::Instant)> = + quick_cache::sync::Cache::new(500); +} + +const EMAIL_CACHE_TTL_SECS: u64 = 60; + +/// Get email from permissioned_as string. +/// - "u/{username}" → lookup email from usr table (cached) +/// - "g/{group}" → "group-{group}@windmill.dev" +/// - raw email → return as-is +pub async fn get_email_from_permissioned_as( + permissioned_as: &str, + workspace_id: &str, + db: &sqlx::Pool, +) -> crate::error::Result { + if let Some(username) = permissioned_as.strip_prefix(PERMISSIONED_AS_USER_PREFIX) { + let lookup = EmailCacheKey(workspace_id, username); + if let Some((email, cached_at)) = EMAIL_CACHE.get(&lookup) { + if cached_at.elapsed().as_secs() < EMAIL_CACHE_TTL_SECS { + return Ok(email); + } + } + let email = sqlx::query_scalar!( + "SELECT email FROM usr WHERE username = $1 AND workspace_id = $2", + username, + workspace_id + ) + .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) + } else if let Some(group) = permissioned_as.strip_prefix(PERMISSIONED_AS_GROUP_PREFIX) { + Ok(format!("{}{}@windmill.dev", USERNAME_GROUP_PREFIX, group)) + } else { + // raw email + Ok(permissioned_as.to_string()) } } @@ -29,3 +90,19 @@ pub fn truncate_token(token: &str) -> String { token.to_string() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_username_to_permissioned_as() { + assert_eq!(username_to_permissioned_as("alice"), "u/alice"); + assert_eq!( + username_to_permissioned_as("alice@example.com"), + "alice@example.com" + ); + assert_eq!(username_to_permissioned_as("group-all"), "g/all"); + assert_eq!(username_to_permissioned_as("group-my-team"), "g/my-team"); + } +} diff --git a/backend/windmill-common/tests/notify_events.rs b/backend/windmill-common/tests/notify_events.rs index b9f639caea..50ada6f606 100644 --- a/backend/windmill-common/tests/notify_events.rs +++ b/backend/windmill-common/tests/notify_events.rs @@ -459,8 +459,8 @@ async fn test_trigger_notify_http_trigger_change(db: Pool) { // Insert an HTTP trigger sqlx::query( - "INSERT INTO http_trigger (path, route_path, route_path_key, script_path, is_flow, workspace_id, edited_by, email, http_method, authentication_method) - VALUES ($1, '/test/route', '/test/route', 'test/script', false, 'test-workspace', 'test-user', 'test@test.com', 'get', 'none')", + "INSERT INTO http_trigger (path, route_path, route_path_key, script_path, is_flow, workspace_id, edited_by, permissioned_as, http_method, authentication_method) + VALUES ($1, '/test/route', '/test/route', 'test/script', false, 'test-workspace', 'test-user', 'u/test-user', 'get', 'none')", ) .bind(&trigger_path) .execute(&db) diff --git a/backend/windmill-queue/src/jobs.rs b/backend/windmill-queue/src/jobs.rs index 5537e63128..1aff43a388 100644 --- a/backend/windmill-queue/src/jobs.rs +++ b/backend/windmill-queue/src/jobs.rs @@ -1869,9 +1869,20 @@ pub async fn try_schedule_next_job<'c>( &job.workspace_id ); + let permissioned_as = schedule.permissioned_as.clone(); + let email = match windmill_common::users::get_email_from_permissioned_as( + &permissioned_as, + &job.workspace_id, + db, + ) + .await + { + Ok(email) => email, + Err(e) => return (tx, Some(e)), + }; let schedule_authed = windmill_common::auth::fetch_authed_from_permissioned_as( - &windmill_common::users::username_to_permissioned_as(&schedule.edited_by), - &schedule.email, + &permissioned_as, + &email, &job.workspace_id, &mut *tx, ) @@ -5832,7 +5843,7 @@ async fn push_inner<'c, 'd>( let audit_author = if format!("u/{user}") != permissioned_as && user != permissioned_as { AuditAuthor { email: email.to_string(), - username: permissioned_as.trim_start_matches("u/").to_string(), + username: windmill_common::auth::permissioned_as_to_username(&permissioned_as), username_override: Some(user.to_string()), token_prefix: token_prefix.map(|s| s.to_string()), } diff --git a/backend/windmill-queue/src/schedule.rs b/backend/windmill-queue/src/schedule.rs index d24d51cea9..ddedb0aef9 100644 --- a/backend/windmill-queue/src/schedule.rs +++ b/backend/windmill-queue/src/schedule.rs @@ -344,8 +344,11 @@ pub async fn push_scheduled_job<'c>( .await?; let (debouncing_settings, concurrency_settings) = - windmill_common::runnable_settings::prefetch_cached_from_handle(runnable_settings_handle, db) - .await?; + windmill_common::runnable_settings::prefetch_cached_from_handle( + runnable_settings_handle, + db, + ) + .await?; if schedule.retry.is_some() { let parsed_retry = serde_json::from_value::(schedule.retry.clone().unwrap()) @@ -450,18 +453,20 @@ pub async fn push_scheduled_job<'c>( .await?; } ( - email, + email.clone(), username_to_permissioned_as(&created_by), None, is_windmill_user, ) } else { - ( - &schedule.email, - username_to_permissioned_as(&schedule.edited_by), - authed, - false, + let permissioned_as = schedule.permissioned_as.clone(); + let resolved_email = windmill_common::users::get_email_from_permissioned_as( + &permissioned_as, + &schedule.workspace_id, + db, ) + .await?; + (resolved_email, permissioned_as, authed, false) }; let obo_authed; @@ -470,7 +475,7 @@ pub async fn push_scheduled_job<'c>( None => { obo_authed = windmill_common::auth::fetch_authed_from_permissioned_as( &permissioned_as, - email, + &email, &schedule.workspace_id, &mut *tx, ) @@ -485,7 +490,7 @@ pub async fn push_scheduled_job<'c>( &db, &schedule.workspace_id, &tag, - email, + &email, None, // no token for schedules so no scopes so no scope_tags ) .warn_after_seconds_with_sql(1, "check_tag_available_for_workspace_internal".to_string()) @@ -506,7 +511,7 @@ pub async fn push_scheduled_job<'c>( payload, crate::PushArgs { args: &args, extra: None }, &schedule_to_user(&schedule.path), - email, + &email, permissioned_as, Some(&schedule.path), Some(next), diff --git a/backend/windmill-queue/tests/schedule_push.rs b/backend/windmill-queue/tests/schedule_push.rs index f946234670..bef9f81f36 100644 --- a/backend/windmill-queue/tests/schedule_push.rs +++ b/backend/windmill-queue/tests/schedule_push.rs @@ -23,6 +23,7 @@ mod schedule_push { args: None, extra_perms: serde_json::json!({}), email: "test@windmill.dev".to_string(), + permissioned_as: "u/test-user".to_string(), error: None, on_failure: None, on_failure_times: None, @@ -423,8 +424,7 @@ mod schedule_push { // Pass a now_cutoff far in the future — simulates clock shift let future_cutoff = Utc::now() + chrono::Duration::hours(24); let tx = db.begin().await?; - let tx = - push_scheduled_job(&db, tx, &schedule, Some(&authed), Some(future_cutoff)).await?; + let tx = push_scheduled_job(&db, tx, &schedule, Some(&authed), Some(future_cutoff)).await?; tx.commit().await?; assert_eq!(count_queued_jobs(&db).await, 1); @@ -451,14 +451,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; tx.commit().await?; assert!(err.is_none()); assert_eq!(count_queued_jobs(&db).await, 0); @@ -475,14 +469,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - "f/system/different_script", - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, "f/system/different_script").await; tx.commit().await?; assert!(err.is_none()); assert_eq!(count_queued_jobs(&db).await, 0); @@ -499,14 +487,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; tx.commit().await?; assert!(err.is_none()); assert_eq!(count_queued_jobs(&db).await, 1); @@ -531,14 +513,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; tx.commit().await?; assert!(err.is_none()); assert_eq!(count_queued_jobs(&db).await, 1); @@ -560,8 +536,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_handle_push_failure_disables_schedule(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -573,14 +549,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; // NotFound: schedule disabled internally, no error returned (caller commits) assert!(err.is_none()); tx.commit().await?; @@ -605,8 +575,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_success_atomic_with_commit(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -615,14 +585,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; assert!(err.is_none()); tx.commit().await?; @@ -650,14 +614,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; assert!(err.is_none()); // Intentionally drop tx without committing (simulates caller failure) @@ -676,8 +634,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failure_disable_rolls_back_on_drop(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -689,14 +647,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; // NotFound: schedule disabled in tx, no error returned assert!(err.is_none()); @@ -725,29 +677,24 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (mut tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (mut tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; assert!(err.is_none()); // Write something else on the same tx - sqlx::query("INSERT INTO global_settings (name, value) VALUES ('_test_after_push', '42'::jsonb)") - .execute(&mut *tx) - .await?; + sqlx::query( + "INSERT INTO global_settings (name, value) VALUES ('_test_after_push', '42'::jsonb)", + ) + .execute(&mut *tx) + .await?; tx.commit().await?; // Both the pushed job and the extra write should be visible assert_eq!(count_queued_jobs(&db).await, 1); - let val: serde_json::Value = sqlx::query_scalar( - "SELECT value FROM global_settings WHERE name = '_test_after_push'", - ) - .fetch_one(&db) - .await?; + let val: serde_json::Value = + sqlx::query_scalar("SELECT value FROM global_settings WHERE name = '_test_after_push'") + .fetch_one(&db) + .await?; assert_eq!(val, serde_json::json!(42)); Ok(()) } @@ -760,8 +707,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_tx_usable_after_failure(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -773,21 +720,17 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (mut tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (mut tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; // NotFound: schedule disabled in tx, no error returned assert!(err.is_none()); // Write something else on the returned tx — tx is still usable - sqlx::query("INSERT INTO global_settings (name, value) VALUES ('_test_after_fail', '99'::jsonb)") - .execute(&mut *tx) - .await?; + sqlx::query( + "INSERT INTO global_settings (name, value) VALUES ('_test_after_fail', '99'::jsonb)", + ) + .execute(&mut *tx) + .await?; tx.commit().await?; // Schedule should be disabled (NotFound is non-retryable) @@ -800,11 +743,10 @@ mod schedule_push { assert!(error.is_some()); // Extra write should still be committed (tx is usable after push failure) - let val: serde_json::Value = sqlx::query_scalar( - "SELECT value FROM global_settings WHERE name = '_test_after_fail'", - ) - .fetch_one(&db) - .await?; + let val: serde_json::Value = + sqlx::query_scalar("SELECT value FROM global_settings WHERE name = '_test_after_fail'") + .fetch_one(&db) + .await?; assert_eq!(val, serde_json::json!(99)); Ok(()) } @@ -823,14 +765,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; tx.commit().await?; assert!(err.is_none()); assert_eq!(count_queued_jobs(&db).await, 1); @@ -855,14 +791,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; tx.commit().await?; assert!(err.is_none()); assert_eq!(count_queued_jobs(&db).await, 1); @@ -883,8 +813,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_push_failure_stores_error_message(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -896,14 +826,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; // NotFound: schedule disabled in tx, no error returned (caller commits) assert!(err.is_none()); tx.commit().await?; @@ -928,8 +852,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_disabled_schedule_no_side_effects(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/disabled_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', false, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/disabled_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', false, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -941,14 +865,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; tx.commit().await?; assert!(err.is_none()); @@ -959,7 +877,10 @@ mod schedule_push { .fetch_one(&db) .await?; assert!(!enabled); - assert!(error.is_none(), "disabled schedule should not get an error set"); + assert!( + error.is_none(), + "disabled schedule should not get an error set" + ); assert_eq!(count_queued_jobs(&db).await, 0); Ok(()) } @@ -971,8 +892,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_path_mismatch_no_side_effects(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -981,14 +902,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - "f/system/different_script", - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, "f/system/different_script").await; tx.commit().await?; assert!(err.is_none()); @@ -1020,14 +935,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; drop(tx); // NotFound: disable succeeds (UPDATE 0 rows is not an error), no error returned assert!(err.is_none()); @@ -1045,8 +954,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_invariant_success_means_tick_committed(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1055,14 +964,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; assert!(err.is_none()); tx.commit().await?; @@ -1092,8 +995,8 @@ mod schedule_push { db: Pool, ) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1105,14 +1008,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; // NotFound: schedule disabled in tx, no error returned (caller commits) assert!(err.is_none()); tx.commit().await?; @@ -1142,8 +1039,8 @@ mod schedule_push { db: Pool, ) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1155,14 +1052,8 @@ mod schedule_push { let job = make_completed_job(&schedule); let tx = db.begin().await?; - let (tx, err) = try_schedule_next_job( - &db, - tx, - &job, - &schedule, - &schedule.script_path, - ) - .await; + let (tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path).await; // NotFound: schedule disabled in tx, no error returned assert!(err.is_none()); @@ -1195,10 +1086,12 @@ mod schedule_push { // --------------------------------------------------------------- #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] - async fn test_failpoint_savepoint_create_disables(db: Pool) -> anyhow::Result<()> { + async fn test_failpoint_savepoint_create_disables( + db: Pool, + ) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1234,8 +1127,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_push_disables(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1269,10 +1162,12 @@ mod schedule_push { // --------------------------------------------------------------- #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] - async fn test_failpoint_savepoint_commit_disables(db: Pool) -> anyhow::Result<()> { + async fn test_failpoint_savepoint_commit_disables( + db: Pool, + ) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1306,10 +1201,12 @@ mod schedule_push { // --------------------------------------------------------------- #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] - async fn test_failpoint_schedule_disable_returns_err(db: Pool) -> anyhow::Result<()> { + async fn test_failpoint_schedule_disable_returns_err( + db: Pool, + ) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1320,13 +1217,15 @@ mod schedule_push { }); let job = make_completed_job(&schedule); - ACTIVE.scope(ScheduleFailPoint::ScheduleDisable, async { - let tx = db.begin().await.unwrap(); - let (_tx, err) = try_schedule_next_job( - &db, tx, &job, &schedule, &schedule.script_path, - ).await; - assert!(err.is_some(), "must return Some(err) when disable fails"); - }).await; + ACTIVE + .scope(ScheduleFailPoint::ScheduleDisable, async { + let tx = db.begin().await.unwrap(); + let (_tx, err) = + try_schedule_next_job(&db, tx, &job, &schedule, &schedule.script_path) + .await; + assert!(err.is_some(), "must return Some(err) when disable fails"); + }) + .await; Ok(()) } @@ -1337,8 +1236,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_disable_failure_rollback(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/bad_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/nonexistent', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1375,10 +1274,12 @@ mod schedule_push { // --------------------------------------------------------------- #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] - async fn test_failpoint_push_quota_exceeded_script(db: Pool) -> anyhow::Result<()> { + async fn test_failpoint_push_quota_exceeded_script( + db: Pool, + ) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_script', false, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1416,8 +1317,8 @@ mod schedule_push { #[sqlx::test(migrations = "../migrations", fixtures("base", "schedule_push"))] async fn test_failpoint_push_quota_exceeded_flow(db: Pool) -> anyhow::Result<()> { sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/flow_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/flow_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1481,8 +1382,8 @@ mod schedule_push { // Schedule still enabled — simulates both push and disable failing sqlx::query( - "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap) - VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false)" + "INSERT INTO schedule (workspace_id, path, edited_by, edited_at, schedule, timezone, enabled, script_path, is_flow, email, extra_perms, ws_error_handler_muted, no_flow_overlap, permissioned_as) + VALUES ('test-workspace', 'f/system/test_schedule', 'test-user', now(), '0 0 */5 * * *', 'UTC', true, 'f/system/test_flow', true, 'test@windmill.dev', '{}', false, false, 'u/test-user')" ) .execute(&db) .await?; @@ -1583,14 +1484,16 @@ mod schedule_push { .await?; // Flow is re-queued for processing - let (running, started_at): (bool, Option>) = sqlx::query_as( - "SELECT running, started_at FROM v2_job_queue WHERE id = $1", - ) - .bind(flow_job_id) - .fetch_one(&db) - .await?; + let (running, started_at): (bool, Option>) = + sqlx::query_as("SELECT running, started_at FROM v2_job_queue WHERE id = $1") + .bind(flow_job_id) + .fetch_one(&db) + .await?; assert!(!running, "flow must not be running after zombie restart"); - assert!(started_at.is_none(), "started_at must be null after zombie restart"); + assert!( + started_at.is_none(), + "started_at must be null after zombie restart" + ); // Schedule still enabled — will be retried when flow re-executes let enabled: bool = sqlx::query_scalar( @@ -1598,16 +1501,21 @@ mod schedule_push { ) .fetch_one(&db) .await?; - assert!(enabled, "schedule must remain enabled for retry after zombie restart"); + assert!( + enabled, + "schedule must remain enabled for retry after zombie restart" + ); // Flow job is NOT in v2_job_completed (it was never completed with error) - let completed_count: i64 = sqlx::query_scalar( - "SELECT COUNT(*) FROM v2_job_completed WHERE id = $1", - ) - .bind(flow_job_id) - .fetch_one(&db) - .await?; - assert_eq!(completed_count, 0, "flow must not be in completed_job — it's a zombie, not an error"); + let completed_count: i64 = + sqlx::query_scalar("SELECT COUNT(*) FROM v2_job_completed WHERE id = $1") + .bind(flow_job_id) + .fetch_one(&db) + .await?; + assert_eq!( + completed_count, 0, + "flow must not be in completed_job — it's a zombie, not an error" + ); Ok(()) } diff --git a/backend/windmill-trigger-http/src/handler.rs b/backend/windmill-trigger-http/src/handler.rs index e8bc512011..c7ca7431fd 100644 --- a/backend/windmill-trigger-http/src/handler.rs +++ b/backend/windmill-trigger-http/src/handler.rs @@ -138,7 +138,7 @@ fn check_no_duplicates( pub async fn insert_new_trigger_into_db( authed: &ApiAuthed, - db: &DB, + _db: &DB, tx: &mut PgConnection, w_id: &str, trigger: &TriggerData, @@ -148,7 +148,7 @@ pub async fn insert_new_trigger_into_db( let request_type = trigger.config.request_type; let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); sqlx::query!( r#" @@ -171,7 +171,7 @@ pub async fn insert_new_trigger_into_db( http_method, static_asset_config, edited_by, - email, + permissioned_as, edited_at, is_static_website, error_handler_path, @@ -200,7 +200,7 @@ pub async fn insert_new_trigger_into_db( trigger.config.http_method as _, trigger.config.static_asset_config as _, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, trigger.config.is_static_website, trigger.error_handling.error_handler_path, trigger.error_handling.error_handler_args as _, @@ -430,7 +430,7 @@ impl TriggerCrud for HttpTrigger { trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, workspace_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); if authed.is_admin { if trigger.config.route_path.is_empty() { @@ -465,7 +465,7 @@ impl TriggerCrud for HttpTrigger { http_method = $11, static_asset_config = $12, edited_by = $13, - email = $14, + permissioned_as = $14, request_type = $15, authentication_method = $16, summary = $17, @@ -492,7 +492,7 @@ impl TriggerCrud for HttpTrigger { trigger.config.http_method as _, trigger.config.static_asset_config as _, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, request_type as _, trigger.config.authentication_method as _, trigger.config.summary, @@ -524,7 +524,7 @@ impl TriggerCrud for HttpTrigger { http_method = $8, static_asset_config = $9, edited_by = $10, - email = $11, + permissioned_as = $11, request_type = $12, authentication_method = $13, summary = $14, @@ -548,7 +548,7 @@ impl TriggerCrud for HttpTrigger { trigger.config.http_method as _, trigger.config.static_asset_config as _, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, request_type as _, trigger.config.authentication_method as _, trigger.config.summary, diff --git a/backend/windmill-trigger-http/src/http_trigger_auth.rs b/backend/windmill-trigger-http/src/http_trigger_auth.rs index 13462f9156..987d0ac811 100644 --- a/backend/windmill-trigger-http/src/http_trigger_auth.rs +++ b/backend/windmill-trigger-http/src/http_trigger_auth.rs @@ -858,7 +858,13 @@ mod tests { let sig = calculate_hmac_signature(HmacAlgorithm::Sha256, secret, payload); let encoded = encode_hmac_signature(Encoding::Hex, &sig); - let data = make_auth_data(payload, &encoded, None, HmacAlgorithm::Sha256, Encoding::Hex); + let data = make_auth_data( + payload, + &encoded, + None, + HmacAlgorithm::Sha256, + Encoding::Hex, + ); assert!(verify_hmac_signature(data, secret).is_ok()); } @@ -886,7 +892,13 @@ mod tests { let sig = calculate_hmac_signature(HmacAlgorithm::Sha512, secret, payload); let encoded = encode_hmac_signature(Encoding::Hex, &sig); - let data = make_auth_data(payload, &encoded, None, HmacAlgorithm::Sha512, Encoding::Hex); + let data = make_auth_data( + payload, + &encoded, + None, + HmacAlgorithm::Sha512, + Encoding::Hex, + ); assert!(verify_hmac_signature(data, secret).is_ok()); } @@ -939,7 +951,13 @@ mod tests { let sig = calculate_hmac_signature(HmacAlgorithm::Sha256, secret, payload); let encoded = encode_hmac_signature(Encoding::Hex, &sig); - let data = make_auth_data(payload, &encoded, None, HmacAlgorithm::Sha256, Encoding::Hex); + let data = make_auth_data( + payload, + &encoded, + None, + HmacAlgorithm::Sha256, + Encoding::Hex, + ); let result = verify_hmac_signature(data, "wrong_key"); assert!(matches!(result, Err(AuthenticationError::InvalidSignature))); } @@ -1045,10 +1063,7 @@ mod tests { #[test] fn test_hmac_algorithm_serde() { - assert_eq!( - serde_json::to_value(HmacAlgorithm::Sha1).unwrap(), - "sha1" - ); + assert_eq!(serde_json::to_value(HmacAlgorithm::Sha1).unwrap(), "sha1"); assert_eq!( serde_json::to_value(HmacAlgorithm::Sha256).unwrap(), "sha256" @@ -1339,7 +1354,13 @@ mod tests { fn test_twitch_authenticate_valid_notification() { let secret = "twitch_secret"; let payload = r#"{"subscription":{},"event":{"user_id":"123"}}"#.to_string(); - let headers = twitch_headers(secret, &payload, "msg-123", "2024-01-01T00:00:00Z", "notification"); + let headers = twitch_headers( + secret, + &payload, + "msg-123", + "2024-01-01T00:00:00Z", + "notification", + ); let method = AuthenticationMethod::Signature(SignatureAuthentication { signature_provider: WebhookType::Twitch, @@ -1441,7 +1462,8 @@ mod tests { #[test] fn test_zoom_non_challenge_returns_none() { - let payload = r#"{"event":"meeting.started","event_ts":1234567890,"payload":{"plainToken":"abc"}}"#; + let payload = + r#"{"event":"meeting.started","event_ts":1234567890,"payload":{"plainToken":"abc"}}"#; let handler = WebhookType::Zoom.get_webhook_handler().unwrap(); let config_data = SignatureConfigData { secret_key: "secret" }; @@ -1461,10 +1483,7 @@ mod tests { let encoded = encode_hmac_signature(Encoding::Hex, &sig); let mut headers = HeaderMap::new(); - headers.insert( - "X-My-Signature", - HeaderValue::from_str(&encoded).unwrap(), - ); + headers.insert("X-My-Signature", HeaderValue::from_str(&encoded).unwrap()); let method = AuthenticationMethod::Signature(SignatureAuthentication { signature_provider: WebhookType::Custom, @@ -1736,8 +1755,7 @@ mod tests { #[test] fn test_error_missing_header_is_400() { - let response = - AuthenticationError::MissingHeader("X-Sig".to_string()).into_response(); + let response = AuthenticationError::MissingHeader("X-Sig".to_string()).into_response(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); } diff --git a/backend/windmill-trigger-http/src/lib.rs b/backend/windmill-trigger-http/src/lib.rs index 69acfcc2af..f5fd563ab3 100644 --- a/backend/windmill-trigger-http/src/lib.rs +++ b/backend/windmill-trigger-http/src/lib.rs @@ -39,7 +39,7 @@ pub struct TriggerRoute { pub request_type: RequestType, pub authentication_method: AuthenticationMethod, pub edited_by: String, - pub email: String, + pub permissioned_as: String, pub static_asset_config: Option>, pub is_static_website: bool, pub authentication_resource_path: Option, @@ -251,7 +251,7 @@ pub async fn refresh_routers(db: &DB) -> Result<(bool, RwLockReadGuard<'_, Route request_type AS "request_type: _", authentication_method AS "authentication_method: _", edited_by, - email, + permissioned_as, static_asset_config AS "static_asset_config: _", wrap_body, raw_string, @@ -459,7 +459,13 @@ mod tests { #[test] fn test_http_method_serde_roundtrip() { - for method in [HttpMethod::Get, HttpMethod::Post, HttpMethod::Put, HttpMethod::Delete, HttpMethod::Patch] { + for method in [ + HttpMethod::Get, + HttpMethod::Post, + HttpMethod::Put, + HttpMethod::Delete, + HttpMethod::Patch, + ] { let json = serde_json::to_value(method).unwrap(); let deserialized: HttpMethod = serde_json::from_value(json).unwrap(); assert_eq!(method, deserialized); @@ -487,7 +493,10 @@ mod tests { fn test_request_type_serialize_values() { assert_eq!(serde_json::to_value(RequestType::Sync).unwrap(), "sync"); assert_eq!(serde_json::to_value(RequestType::Async).unwrap(), "async"); - assert_eq!(serde_json::to_value(RequestType::SyncSse).unwrap(), "sync_sse"); + assert_eq!( + serde_json::to_value(RequestType::SyncSse).unwrap(), + "sync_sse" + ); } // --- AuthenticationMethod serde --- @@ -523,8 +532,13 @@ mod tests { #[test] fn test_validate_auth_custom_script_requires_raw() { assert!(validate_authentication_method(AuthenticationMethod::CustomScript, None).is_err()); - assert!(validate_authentication_method(AuthenticationMethod::CustomScript, Some(false)).is_err()); - assert!(validate_authentication_method(AuthenticationMethod::CustomScript, Some(true)).is_ok()); + assert!( + validate_authentication_method(AuthenticationMethod::CustomScript, Some(false)) + .is_err() + ); + assert!( + validate_authentication_method(AuthenticationMethod::CustomScript, Some(true)).is_ok() + ); } #[test] diff --git a/backend/windmill-trigger-mqtt/src/handler.rs b/backend/windmill-trigger-mqtt/src/handler.rs index 9704d488bf..6edcfbabc3 100644 --- a/backend/windmill-trigger-mqtt/src/handler.rs +++ b/backend/windmill-trigger-mqtt/src/handler.rs @@ -66,14 +66,14 @@ impl TriggerCrud for MqttTrigger { async fn create_trigger( &self, - db: &DB, + _db: &DB, tx: &mut PgConnection, authed: &ApiAuthed, w_id: &str, trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); let subscribe_topics = trigger .config .subscribe_topics @@ -96,7 +96,7 @@ impl TriggerCrud for MqttTrigger { path, script_path, is_flow, - email, + permissioned_as, mode, edited_by, error_handler_path, @@ -116,7 +116,7 @@ impl TriggerCrud for MqttTrigger { trigger.base.path, trigger.base.script_path, trigger.base.is_flow, - resolved_email, + resolved_permissioned_as, trigger.base.mode() as _, &resolved_edited_by, trigger.error_handling.error_handler_path, @@ -131,7 +131,7 @@ impl TriggerCrud for MqttTrigger { async fn update_trigger( &self, - db: &DB, + _db: &DB, tx: &mut PgConnection, authed: &ApiAuthed, workspace_id: &str, @@ -139,7 +139,7 @@ impl TriggerCrud for MqttTrigger { trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, workspace_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); let subscribe_topics = trigger .config .subscribe_topics @@ -163,7 +163,7 @@ impl TriggerCrud for MqttTrigger { v5_config = $6, is_flow = $7, edited_by = $8, - email = $9, + permissioned_as = $9, script_path = $10, path = $11, edited_at = now(), @@ -184,7 +184,7 @@ impl TriggerCrud for MqttTrigger { v5_config as Option>, trigger.base.is_flow, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, trigger.base.script_path, trigger.base.path, workspace_id, diff --git a/backend/windmill-trigger-postgres/src/handler.rs b/backend/windmill-trigger-postgres/src/handler.rs index 2b6d620ca8..cbb149af52 100644 --- a/backend/windmill-trigger-postgres/src/handler.rs +++ b/backend/windmill-trigger-postgres/src/handler.rs @@ -71,7 +71,7 @@ impl TriggerCrud for PostgresTrigger { trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); let Self::TriggerConfigRequest { postgres_resource_path, publication_name, @@ -122,7 +122,7 @@ impl TriggerCrud for PostgresTrigger { is_flow, mode, edited_by, - email, + permissioned_as, edited_at, error_handler_path, error_handler_args, @@ -140,7 +140,7 @@ impl TriggerCrud for PostgresTrigger { trigger.base.is_flow, trigger.base.mode() as _, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, trigger.error_handling.error_handler_path, trigger.error_handling.error_handler_args as _, trigger.error_handling.retry as _ @@ -160,7 +160,7 @@ impl TriggerCrud for PostgresTrigger { trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); let Self::TriggerConfigRequest { replication_slot_name, publication_name, @@ -223,7 +223,7 @@ impl TriggerCrud for PostgresTrigger { path = $5, is_flow = $6, edited_by = $7, - email = $8, + permissioned_as = $8, edited_at = now(), server_id = NULL, error = NULL, @@ -240,7 +240,7 @@ impl TriggerCrud for PostgresTrigger { trigger.base.path, trigger.base.is_flow, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, w_id, path, trigger.error_handling.error_handler_path, diff --git a/backend/windmill-trigger-websocket/src/handler.rs b/backend/windmill-trigger-websocket/src/handler.rs index a9568512a4..fd0080950d 100644 --- a/backend/windmill-trigger-websocket/src/handler.rs +++ b/backend/windmill-trigger-websocket/src/handler.rs @@ -72,14 +72,14 @@ impl TriggerCrud for WebsocketTrigger { async fn create_trigger( &self, - db: &DB, + _db: &DB, tx: &mut PgConnection, authed: &ApiAuthed, w_id: &str, trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); let filters = trigger .config .filters @@ -108,7 +108,7 @@ impl TriggerCrud for WebsocketTrigger { edited_by, can_return_message, can_return_error_result, - email, + permissioned_as, edited_at, error_handler_path, error_handler_args, @@ -132,7 +132,7 @@ impl TriggerCrud for WebsocketTrigger { &resolved_edited_by, trigger.config.can_return_message, trigger.config.can_return_error_result, - resolved_email, + resolved_permissioned_as, trigger.error_handling.error_handler_path, trigger.error_handling.error_handler_args as _, trigger.error_handling.retry as _ @@ -144,7 +144,7 @@ impl TriggerCrud for WebsocketTrigger { async fn update_trigger( &self, - db: &DB, + _db: &DB, tx: &mut PgConnection, authed: &ApiAuthed, w_id: &str, @@ -152,7 +152,7 @@ impl TriggerCrud for WebsocketTrigger { trigger: TriggerData, ) -> Result<()> { let resolved_edited_by = trigger.base.resolve_edited_by(authed); - let resolved_email = trigger.base.resolve_email(authed, db, w_id).await?; + let resolved_permissioned_as = trigger.base.resolve_permissioned_as(authed); let filters = trigger .config .filters @@ -181,7 +181,7 @@ impl TriggerCrud for WebsocketTrigger { initial_messages = $6, url_runnable_args = $7, edited_by = $8, - email = $9, + permissioned_as = $9, can_return_message = $10, can_return_error_result = $11, edited_at = now(), @@ -205,7 +205,7 @@ impl TriggerCrud for WebsocketTrigger { .map(|v| SqlxJson(serde_json::value::to_raw_value(&v).unwrap())) as Option>>, &resolved_edited_by, - resolved_email, + resolved_permissioned_as, trigger.config.can_return_message, trigger.config.can_return_error_result, w_id, diff --git a/backend/windmill-trigger/src/global_handler.rs b/backend/windmill-trigger/src/global_handler.rs index 8c8ab70d5c..84058f5baa 100644 --- a/backend/windmill-trigger/src/global_handler.rs +++ b/backend/windmill-trigger/src/global_handler.rs @@ -23,7 +23,7 @@ pub struct SuspendedTrigger { pub script_path: String, pub is_flow: bool, pub edited_by: String, - pub email: String, + pub permissioned_as: String, pub edited_at: DateTime, pub error_handler_path: Option, pub error_handler_args: Option>>, @@ -52,7 +52,7 @@ async fn get_suspended_trigger( "script_path", "is_flow", "edited_by", - "email", + "permissioned_as", "edited_at", "error_handler_path", "error_handler_args", diff --git a/backend/windmill-trigger/src/handler.rs b/backend/windmill-trigger/src/handler.rs index dcdb53b83f..1f89be9419 100644 --- a/backend/windmill-trigger/src/handler.rs +++ b/backend/windmill-trigger/src/handler.rs @@ -144,7 +144,7 @@ pub trait TriggerCrud: Send + Sync + 'static { "script_path", "is_flow", "edited_by", - "email", + "permissioned_as", "edited_at", "extra_perms", "mode", @@ -222,6 +222,7 @@ pub trait TriggerCrud: Send + Sync + 'static { path: &str, mode: &TriggerMode, ) -> Result { + let permissioned_as = windmill_common::users::username_to_permissioned_as(&authed.username); let updated = if Self::SUPPORTS_SERVER_STATE { sqlx::query(&format!( r#" @@ -229,7 +230,7 @@ pub trait TriggerCrud: Send + Sync + 'static { {} SET mode = $1, - email = $2, + permissioned_as = $2, edited_by = $3, edited_at = now(), server_id = NULL, @@ -241,7 +242,7 @@ pub trait TriggerCrud: Send + Sync + 'static { Self::TABLE_NAME )) .bind(mode) - .bind(&authed.email) + .bind(&permissioned_as) .bind(&authed.username) .bind(workspace_id) .bind(path) @@ -255,7 +256,7 @@ pub trait TriggerCrud: Send + Sync + 'static { {} SET mode = $1, - email = $2, + permissioned_as = $2, edited_by = $3, edited_at = now() WHERE @@ -265,7 +266,7 @@ pub trait TriggerCrud: Send + Sync + 'static { Self::TABLE_NAME )) .bind(mode) - .bind(&authed.email) + .bind(&permissioned_as) .bind(&authed.username) .bind(workspace_id) .bind(path) @@ -322,7 +323,7 @@ pub trait TriggerCrud: Send + Sync + 'static { "script_path", "is_flow", "edited_by", - "email", + "permissioned_as", "edited_at", "extra_perms", "mode", @@ -417,8 +418,8 @@ async fn create_trigger( let new_path = new_trigger.base.path.clone(); let on_behalf_of_info = windmill_common::check_on_behalf_of_preservation( - new_trigger.base.email.as_deref(), - new_trigger.base.preserve_email.unwrap_or(false), + new_trigger.base.permissioned_as.as_deref(), + new_trigger.base.preserve_permissioned_as.unwrap_or(false), &authed, &authed.username, ); @@ -535,8 +536,8 @@ async fn update_trigger( let new_path = edit_trigger.base.path.to_string(); let on_behalf_of_info = windmill_common::check_on_behalf_of_preservation( - edit_trigger.base.email.as_deref(), - edit_trigger.base.preserve_email.unwrap_or(false), + edit_trigger.base.permissioned_as.as_deref(), + edit_trigger.base.preserve_permissioned_as.unwrap_or(false), &authed, &authed.username, ); diff --git a/backend/windmill-trigger/src/listener.rs b/backend/windmill-trigger/src/listener.rs index 04e1df102d..383a7ef174 100644 --- a/backend/windmill-trigger/src/listener.rs +++ b/backend/windmill-trigger/src/listener.rs @@ -15,7 +15,7 @@ use serde_json::value::RawValue; use sql_builder::SqlBuilder; use sqlx::{FromRow, Row}; use tokio::sync::RwLock; -use windmill_api_auth::{fetch_api_authed, ApiAuthed}; +use windmill_api_auth::ApiAuthed; use windmill_common::{ error::{Error, Result}, jobs::JobTriggerKind, @@ -62,7 +62,7 @@ pub trait Listener: TriggerCrud + TriggerJobArgs { "script_path", "is_flow", "edited_by", - "email", + "permissioned_as", "edited_at", "extra_perms", "mode", @@ -98,8 +98,8 @@ pub trait Listener: TriggerCrud + TriggerJobArgs { path: trigger.base.path, workspace_id: trigger.base.workspace_id, is_flow: trigger.base.is_flow, - username: trigger.base.edited_by, - email: trigger.base.email, + edited_by: trigger.base.edited_by, + permissioned_as: trigger.base.permissioned_as, script_path: trigger.base.script_path, trigger_config: trigger.config, error_handling: Some(trigger.error_handling), @@ -120,7 +120,6 @@ pub trait Listener: TriggerCrud + TriggerJobArgs { "is_flow", "workspace_id", "owner AS username", - "email", "trigger_config", ]; @@ -144,17 +143,21 @@ pub trait Listener: TriggerCrud + TriggerJobArgs { let captures = captures .into_iter() - .map(|capture| ListeningTrigger { - username: capture.username, - path: capture.path, - workspace_id: capture.workspace_id, - script_path: "".to_string(), - email: capture.email, - trigger_config: capture.trigger_config, - trigger_mode: false, - is_flow: capture.is_flow, - error_handling: None, - suspended_mode: false, + .map(|capture| { + let permissioned_as = + windmill_common::users::username_to_permissioned_as(&capture.username); + ListeningTrigger { + edited_by: capture.username, + path: capture.path, + workspace_id: capture.workspace_id, + script_path: "".to_string(), + permissioned_as, + trigger_config: capture.trigger_config, + trigger_mode: false, + is_flow: capture.is_flow, + error_handling: None, + suspended_mode: false, + } }) .collect_vec(); @@ -571,7 +574,7 @@ pub trait Listener: TriggerCrud + TriggerJobArgs { &Self::TRIGGER_KIND, main_args, preprocessor_args, - &listening_trigger.username, + &listening_trigger.edited_by, ) .await { @@ -792,7 +795,6 @@ where is_flow: bool, workspace_id: String, username: String, - email: String, #[serde(flatten)] trigger_config: T, } @@ -810,7 +812,6 @@ where is_flow: row.try_get("is_flow")?, workspace_id: row.try_get("workspace_id")?, username: row.try_get("username")?, - email: row.try_get("email")?, trigger_config, }) } @@ -821,8 +822,8 @@ pub struct ListeningTrigger { pub path: String, pub is_flow: bool, pub workspace_id: String, - pub username: String, - pub email: String, + pub edited_by: String, + pub permissioned_as: String, pub trigger_config: T, pub script_path: String, pub trigger_mode: bool, @@ -831,13 +832,19 @@ pub struct ListeningTrigger { } impl ListeningTrigger { - pub async fn authed(&self, db: &DB, username: &str) -> Result { - fetch_api_authed( - self.username.clone(), - self.email.clone(), + pub async fn authed(&self, db: &DB, trigger_kind: &str) -> Result { + let email = windmill_common::users::get_email_from_permissioned_as( + &self.permissioned_as, &self.workspace_id, db, - Some(format!("{}-{}", username, self.path)), + ) + .await?; + windmill_api_auth::fetch_api_authed_from_permissioned_as( + self.permissioned_as.clone(), + email, + &self.workspace_id, + db, + Some(format!("{}-{}", trigger_kind, self.path)), ) .await } diff --git a/backend/windmill-trigger/src/types.rs b/backend/windmill-trigger/src/types.rs index bbfdf30412..eb4072fdf8 100644 --- a/backend/windmill-trigger/src/types.rs +++ b/backend/windmill-trigger/src/types.rs @@ -8,9 +8,9 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use sqlx::{types::Json as SqlxJson, FromRow, Pool, Postgres}; +use sqlx::{types::Json as SqlxJson, FromRow}; use std::{collections::HashMap, fmt::Debug}; -use windmill_common::{db::Authable, error::Result, jobs::JobTriggerKind}; +use windmill_common::{db::Authable, jobs::JobTriggerKind}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] @@ -36,7 +36,7 @@ pub struct BaseTrigger { pub mode: TriggerMode, pub is_flow: bool, pub edited_by: String, - pub email: String, + pub permissioned_as: String, pub edited_at: DateTime, pub extra_perms: Option, } @@ -103,12 +103,12 @@ pub struct BaseTriggerData { #[deprecated(note = "Use mode instead")] enabled: Option, // Kept for backwards compatibility, use mode instead mode: Option, - /// Optional email for deployment - when set, the trigger will run jobs as this user + /// Optional permissioned_as for deployment - when set, the trigger will run jobs as this user #[serde(skip_serializing_if = "Option::is_none")] - pub email: Option, - /// If true and user is admin/wm_deployers, preserve the provided email instead of using deploying user's email + pub permissioned_as: Option, + /// If true and user is admin/wm_deployers, preserve the provided permissioned_as instead of using deploying user's #[serde(skip_serializing_if = "Option::is_none")] - pub preserve_email: Option, + pub preserve_permissioned_as: Option, } impl BaseTriggerData { @@ -123,39 +123,18 @@ impl BaseTriggerData { ) } - pub async fn resolve_email( - &self, - authed: &impl Authable, - db: &Pool, - w_id: &str, - ) -> Result { - if let Some(ref username) = self.email { - if self.preserve_email.unwrap_or(false) + pub fn resolve_permissioned_as(&self, authed: &impl Authable) -> String { + if let Some(ref permissioned_as) = self.permissioned_as { + if self.preserve_permissioned_as.unwrap_or(false) && windmill_common::can_preserve_on_behalf_of(authed) { - let email = sqlx::query_scalar!( - "SELECT email FROM usr WHERE username = $1 AND workspace_id = $2", - username, - w_id - ) - .fetch_optional(db) - .await?; - if let Some(email) = email { - return Ok(email); - } + return permissioned_as.clone(); } } - Ok(authed.email().to_string()) + windmill_common::users::username_to_permissioned_as(authed.username()) } pub fn resolve_edited_by(&self, authed: &impl Authable) -> String { - if let Some(ref username) = self.email { - if self.preserve_email.unwrap_or(false) - && windmill_common::can_preserve_on_behalf_of(authed) - { - return username.clone(); - } - } authed.username().to_string() } } diff --git a/backend/windmill-types/src/schedule.rs b/backend/windmill-types/src/schedule.rs index a3a61b5765..c83df39870 100644 --- a/backend/windmill-types/src/schedule.rs +++ b/backend/windmill-types/src/schedule.rs @@ -18,6 +18,7 @@ pub struct Schedule { pub args: Option>>, pub extra_perms: serde_json::Value, pub email: String, + pub permissioned_as: String, #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/cli/src/guidance/skills.ts b/cli/src/guidance/skills.ts index a3867055da..6422b57a4f 100644 --- a/cli/src/guidance/skills.ts +++ b/cli/src/guidance/skills.ts @@ -5479,6 +5479,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -5560,6 +5563,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - gcp_resource_path - topic_id @@ -5572,6 +5576,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -5683,6 +5690,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - route_path - request_type @@ -5698,6 +5706,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -5775,6 +5786,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - kafka_resource_path - group_id @@ -5786,6 +5798,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -5863,6 +5878,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - subscribe_topics - mqtt_resource_path @@ -5872,6 +5888,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -5935,6 +5954,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - nats_resource_path - use_jetstream @@ -5945,6 +5965,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -6001,6 +6024,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - postgres_resource_path - replication_slot_name @@ -6027,6 +6051,9 @@ properties: args: type: object description: The arguments to pass to the script or flow + permissioned_as: + type: string + description: The user or group this schedule runs as (e.g., 'u/admin' or 'g/mygroup') on_failure: type: string description: Path to a script or flow to run when the scheduled job fails @@ -6128,12 +6155,16 @@ required: - timezone - is_flow - enabled +- permissioned_as `, "sqs_trigger": `type: object properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -6196,6 +6227,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - queue_url - aws_resource_path @@ -6206,6 +6238,9 @@ properties: script_path: type: string description: Path to the script or flow to execute when triggered + permissioned_as: + type: string + description: The user or group this trigger runs as (permissioned_as) is_flow: type: boolean description: True if script_path points to a flow, false if it points to a script @@ -6282,6 +6317,7 @@ properties: description: Retry configuration for failed module executions required: - script_path +- permissioned_as - is_flow - url - filters diff --git a/frontend/src/lib/components/CompareWorkspaces.svelte b/frontend/src/lib/components/CompareWorkspaces.svelte index 5c38fc5505..6905ed6931 100644 --- a/frontend/src/lib/components/CompareWorkspaces.svelte +++ b/frontend/src/lib/components/CompareWorkspaces.svelte @@ -44,11 +44,12 @@ import { userWorkspaces, workspaceStore } from '$lib/stores' import type { Kind } from '$lib/utils_deployable' - import { deployItem, getItemValue, getOnBehalfOfEmail } from '$lib/utils_workspace_deploy' + import { deployItem, getItemValue, getOnBehalfOf } from '$lib/utils_workspace_deploy' import Tooltip from './Tooltip.svelte' import OnBehalfOfSelector, { needsOnBehalfOfSelection, - type OnBehalfOfChoice + type OnBehalfOfChoice, + type OnBehalfOfDetails } from './OnBehalfOfSelector.svelte' import { sendUserToast } from '$lib/toast' import { deepEqual } from 'fast-equals' @@ -120,7 +121,7 @@ // Source workspace on_behalf_of emails (keyed by workspace/kind:path) let onBehalfOfInfo = $state>({}) let onBehalfOfChoice = $state>({}) - let customOnBehalfOfEmails = $state>({}) + let customOnBehalfOf = $state>({}) let deployTargetWorkspace = $derived(mergeIntoParent ? parentWorkspaceId : currentWorkspaceId) function getItemKey(diff: WorkspaceItemDiff): string { @@ -191,7 +192,7 @@ if (onBehalfOfInfo[workspacedKey] !== undefined) continue try { - onBehalfOfInfo[workspacedKey] = await getOnBehalfOfEmail( + onBehalfOfInfo[workspacedKey] = await getOnBehalfOf( diff.kind as Kind, diff.path, workspace @@ -203,21 +204,21 @@ } } - // Get source workspace email for an item - function getSourceEmail(itemKey: string): string | undefined { + // Get source workspace on_behalf_of value for an item (email for runnables, permissioned_as for triggers) + function getSourceOnBehalfOf(itemKey: string): string | undefined { const sourceWorkspace = mergeIntoParent ? currentWorkspaceId : parentWorkspaceId return onBehalfOfInfo[getWorkspacedKey(sourceWorkspace, itemKey)] } - // Get target workspace email for an item (existing item in destination) - function getTargetEmail(itemKey: string): string | undefined { + // Get target workspace on_behalf_of value for an item (existing item in destination) + function getTargetOnBehalfOf(itemKey: string): string | undefined { const targetWorkspace = mergeIntoParent ? parentWorkspaceId : currentWorkspaceId return onBehalfOfInfo[getWorkspacedKey(targetWorkspace, itemKey)] } // Check if an item needs on_behalf_of selection function itemNeedsOnBehalfOfSelection(itemKey: string, kind: string): boolean { - return needsOnBehalfOfSelection(kind, getSourceEmail(itemKey)) + return needsOnBehalfOfSelection(kind, getSourceOnBehalfOf(itemKey)) } // Check if all required on_behalf_of selections are made @@ -231,12 +232,18 @@ }) ) - // Get the email to use for deployment based on user's choice - function getOnBehalfOfEmailForDeploy(itemKey: string): string | undefined { + /** + * Get the on_behalf_of value for deployment based on user's choice. + * Returns an email for flows/scripts/apps, or permissioned_as (u/username, g/group) for triggers/schedules. + */ + function getOnBehalfOfForDeploy(itemKey: string, kind: Kind): string | undefined { const choice = onBehalfOfChoice[itemKey] - if (choice === 'target') return getTargetEmail(itemKey) - if (choice === 'custom') return customOnBehalfOfEmails[itemKey] - // 'me' or undefined = don't pass, backend will use deploying user's email + if (choice === 'target') return getTargetOnBehalfOf(itemKey) + if (choice === 'custom') { + const details = customOnBehalfOf[itemKey] + return kind === 'trigger' ? details?.permissionedAs : details?.email + } + // 'me' or undefined = don't pass, backend will use deploying user's identity return undefined } @@ -304,7 +311,7 @@ path, workspaceFrom, workspaceTo: workspaceToDeployTo, - onBehalfOfEmail: getOnBehalfOfEmailForDeploy(statusPath) + onBehalfOf: getOnBehalfOfForDeploy(statusPath, kind) }) if (result.success) { @@ -868,7 +875,7 @@ {#snippet itemActions(item)} {@const diff = item.diff as WorkspaceItemDiff} {@const key = item.key} - {@const targetEmail = getTargetEmail(key)} + {@const targetOnBehalfOf = getTargetOnBehalfOf(key)} {@const isConflict = diff.ahead > 0 && diff.behind > 0} {@const existsInBothWorkspaces = !( (diff.exists_in_fork && !diff.exists_in_source) || @@ -878,15 +885,15 @@ {#if itemNeedsOnBehalfOfSelection(key, diff.kind)} { + onSelect={(choice, details) => { onBehalfOfChoice[key] = choice - if (email) customOnBehalfOfEmails[key] = email + if (details) customOnBehalfOf[key] = details }} kind={diff.kind} canPreserve={canPreserveOnBehalfOf} - customEmail={customOnBehalfOfEmails[key]} + customValue={customOnBehalfOf[key]?.permissionedAs} /> {/if} {#if diff.kind === 'raw_app'} diff --git a/frontend/src/lib/components/DeployWorkspace.svelte b/frontend/src/lib/components/DeployWorkspace.svelte index a947e62336..9f1fd71146 100644 --- a/frontend/src/lib/components/DeployWorkspace.svelte +++ b/frontend/src/lib/components/DeployWorkspace.svelte @@ -26,7 +26,7 @@ checkItemExists, deployItem, getItemValue, - getOnBehalfOfEmail + getOnBehalfOf } from '$lib/utils_workspace_deploy' import type { App } from './apps/types' import { getAllGridItems } from './apps/editor/appUtils' @@ -35,7 +35,8 @@ import WorkspaceDeployLayout from './WorkspaceDeployLayout.svelte' import OnBehalfOfSelector, { needsOnBehalfOfSelection, - type OnBehalfOfChoice + type OnBehalfOfChoice, + type OnBehalfOfDetails } from './OnBehalfOfSelector.svelte' import ParentWorkspaceProtectionAlert from './ParentWorkspaceProtectionAlert.svelte' @@ -77,7 +78,7 @@ // Target workspace on_behalf_of emails (keyed by kind:path) let targetOnBehalfOfInfo = $state>({}) let onBehalfOfChoice = $state>({}) - let customOnBehalfOfEmails = $state>({}) + let customOnBehalfOf = $state>({}) let canPreserveOnBehalfOf = $state(false) // Check if an item needs on_behalf_of selection @@ -85,12 +86,18 @@ return needsOnBehalfOfSelection(kind, sourceOnBehalfOfInfo[statusPath]) } - // Get the email to use for deployment based on user's choice - function getOnBehalfOfEmailForDeploy(statusPath: string): string | undefined { + /** + * Get the on_behalf_of value for deployment based on user's choice. + * Returns an email for flows/scripts/apps, or permissioned_as (u/username, g/group) for triggers/schedules. + */ + function getOnBehalfOfForDeploy(statusPath: string, kind: Kind): string | undefined { const choice = onBehalfOfChoice[statusPath] if (choice === 'target') return targetOnBehalfOfInfo[statusPath] - if (choice === 'custom') return customOnBehalfOfEmails[statusPath] - // 'me' or undefined = don't pass, backend will use deploying user's email + if (choice === 'custom') { + const details = customOnBehalfOf[statusPath] + return kind === 'trigger' ? details?.permissionedAs : details?.email + } + // 'me' or undefined = don't pass, backend will use deploying user's identity return undefined } @@ -145,7 +152,7 @@ )) { const key = computeStatusPath(dep.kind, dep.path) try { - sourceOnBehalfOfInfo[key] = await getOnBehalfOfEmail( + sourceOnBehalfOfInfo[key] = await getOnBehalfOf( dep.kind, dep.path, $workspaceStore!, @@ -155,7 +162,7 @@ sourceOnBehalfOfInfo[key] = undefined } try { - targetOnBehalfOfInfo[key] = await getOnBehalfOfEmail( + targetOnBehalfOfInfo[key] = await getOnBehalfOf( dep.kind, dep.path, workspaceToDeployTo!, @@ -295,7 +302,7 @@ workspaceFrom: $workspaceStore!, workspaceTo: workspaceToDeployTo!, additionalInformation, - onBehalfOfEmail: getOnBehalfOfEmailForDeploy(statusPath) + onBehalfOf: getOnBehalfOfForDeploy(statusPath, kind) }) if (result.success) { @@ -458,21 +465,21 @@ {@const statusPath = item.key} {@const exists = allAlreadyExists[statusPath]} {@const status = deploymentStatus[statusPath]} - {@const targetEmail = targetOnBehalfOfInfo[statusPath]} + {@const targetValue = targetOnBehalfOfInfo[statusPath]} {#if itemNeedsOnBehalfOfSelection(statusPath, item.kind)} { + onSelect={(choice, details) => { onBehalfOfChoice[statusPath] = choice - if (email) customOnBehalfOfEmails[statusPath] = email + if (details) customOnBehalfOf[statusPath] = details }} kind={item.kind} canPreserve={canPreserveOnBehalfOf} - customEmail={customOnBehalfOfEmails[statusPath]} + customValue={customOnBehalfOf[statusPath]?.permissionedAs} /> {/if} @@ -537,10 +544,10 @@ {#if hasUnselectedOnBehalfOf} {#if kind === 'trigger'} - You must set the "edited by" user for all triggers before deploying + You must set the "permissioned as" user for all triggers before deploying - The "edited by" field defines which user's permissions will be applied when the - trigger runs. Make sure this is set to an appropriate user before deploying. + The "permissioned as" field defines which user's permissions will be applied when + the trigger fires. Make sure this is set appropriately before deploying. {:else} You must set the "on behalf of" user for all items before deploying diff --git a/frontend/src/lib/components/GroupEditor.svelte b/frontend/src/lib/components/GroupEditor.svelte index 5a8c4cb411..ab1d3a6d63 100644 --- a/frontend/src/lib/components/GroupEditor.svelte +++ b/frontend/src/lib/components/GroupEditor.svelte @@ -119,9 +119,9 @@
{#if name === 'wm_deployers'} - Members of this group can preserve the original author (on_behalf_of / edited_by) when - deploying scripts, flows, apps, and triggers to this workspace. Without this permission, - deployed items will be reassigned to the deploying user. + Members of this group can preserve the original author (on_behalf_of / permissioned_as) when + deploying scripts, flows, apps, triggers, and schedules to this workspace. Without this + permission, deployed items will be reassigned to the deploying user. {/if}