diff --git a/backend/.sqlx/query-03fdb7d0d4d95a98fdbdcf7dd1a9289119b9ce719d6a6c2975e96d7785819fc9.json b/backend/.sqlx/query-03fdb7d0d4d95a98fdbdcf7dd1a9289119b9ce719d6a6c2975e96d7785819fc9.json new file mode 100644 index 0000000000..d228ed6ae2 --- /dev/null +++ b/backend/.sqlx/query-03fdb7d0d4d95a98fdbdcf7dd1a9289119b9ce719d6a6c2975e96d7785819fc9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE postgres_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "03fdb7d0d4d95a98fdbdcf7dd1a9289119b9ce719d6a6c2975e96d7785819fc9" +} diff --git a/backend/.sqlx/query-099067cf6c08642b205074771ef23d7ab950015d70f7065f5645f1e76d6a25ee.json b/backend/.sqlx/query-099067cf6c08642b205074771ef23d7ab950015d70f7065f5645f1e76d6a25ee.json new file mode 100644 index 0000000000..d71eee6337 --- /dev/null +++ b/backend/.sqlx/query-099067cf6c08642b205074771ef23d7ab950015d70f7065f5645f1e76d6a25ee.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET slack_email = $1 WHERE slack_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "099067cf6c08642b205074771ef23d7ab950015d70f7065f5645f1e76d6a25ee" +} diff --git a/backend/.sqlx/query-0da2425ff8ee737104cd9f2650f1ffba1511715ac1f9270939d79fb82f8e88d0.json b/backend/.sqlx/query-0da2425ff8ee737104cd9f2650f1ffba1511715ac1f9270939d79fb82f8e88d0.json new file mode 100644 index 0000000000..36e9ac3e04 --- /dev/null +++ b/backend/.sqlx/query-0da2425ff8ee737104cd9f2650f1ffba1511715ac1f9270939d79fb82f8e88d0.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE azure_trigger SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "0da2425ff8ee737104cd9f2650f1ffba1511715ac1f9270939d79fb82f8e88d0" +} diff --git a/backend/.sqlx/query-0f49203698dce2d77724c3d2fbaf729c6b2c2f9dd9475b778dbce66f28b48e69.json b/backend/.sqlx/query-0f49203698dce2d77724c3d2fbaf729c6b2c2f9dd9475b778dbce66f28b48e69.json new file mode 100644 index 0000000000..b630ea6c5d --- /dev/null +++ b/backend/.sqlx/query-0f49203698dce2d77724c3d2fbaf729c6b2c2f9dd9475b778dbce66f28b48e69.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE schedule SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "0f49203698dce2d77724c3d2fbaf729c6b2c2f9dd9475b778dbce66f28b48e69" +} diff --git a/backend/.sqlx/query-1159c2beb23057e3123847efa2b5d2d7cd7c89b47cbf5000fb74a9cdaa4b62df.json b/backend/.sqlx/query-1159c2beb23057e3123847efa2b5d2d7cd7c89b47cbf5000fb74a9cdaa4b62df.json new file mode 100644 index 0000000000..b67713ccc0 --- /dev/null +++ b/backend/.sqlx/query-1159c2beb23057e3123847efa2b5d2d7cd7c89b47cbf5000fb74a9cdaa4b62df.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO folder(workspace_id, name, display_name, owners, extra_perms, default_permissioned_as)\n VALUES ('test-workspace', 'fold', 'fold', '{}', '{}'::jsonb,\n '[{\"path_glob\": \"a/**\", \"permissioned_as\": \"u/other\"}, {\"path_glob\": \"**\", \"permissioned_as\": \"test2@windmill.dev\"}]'::jsonb)", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "1159c2beb23057e3123847efa2b5d2d7cd7c89b47cbf5000fb74a9cdaa4b62df" +} diff --git a/backend/.sqlx/query-162ab0cc9094d7031229a65f47d730282d17bc8a71a0e234c9edf81b85199dd1.json b/backend/.sqlx/query-162ab0cc9094d7031229a65f47d730282d17bc8a71a0e234c9edf81b85199dd1.json new file mode 100644 index 0000000000..e3f8e5d178 --- /dev/null +++ b/backend/.sqlx/query-162ab0cc9094d7031229a65f47d730282d17bc8a71a0e234c9edf81b85199dd1.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_fork_deployment_request_comment SET author_email = $1 WHERE author_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "162ab0cc9094d7031229a65f47d730282d17bc8a71a0e234c9edf81b85199dd1" +} diff --git a/backend/.sqlx/query-1b828adc60798b096ebf5db75fc1ade1ffffff492dc02caafe002fd868d90a8d.json b/backend/.sqlx/query-1b828adc60798b096ebf5db75fc1ade1ffffff492dc02caafe002fd868d90a8d.json new file mode 100644 index 0000000000..1b3398b6be --- /dev/null +++ b/backend/.sqlx/query-1b828adc60798b096ebf5db75fc1ade1ffffff492dc02caafe002fd868d90a8d.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE app SET policy = jsonb_set(policy, ARRAY['on_behalf_of'], to_jsonb($1::text)) WHERE policy->>'on_behalf_of' = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "1b828adc60798b096ebf5db75fc1ade1ffffff492dc02caafe002fd868d90a8d" +} diff --git a/backend/.sqlx/query-24e8f955d2a9a3bf502736749563e82974a5744dbb4942c1f2b2dff774594252.json b/backend/.sqlx/query-24e8f955d2a9a3bf502736749563e82974a5744dbb4942c1f2b2dff774594252.json new file mode 100644 index 0000000000..70b6bf874e --- /dev/null +++ b/backend/.sqlx/query-24e8f955d2a9a3bf502736749563e82974a5744dbb4942c1f2b2dff774594252.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT workspace_id FROM usr WHERE email = 'renamed@windmill.dev'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "workspace_id", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "24e8f955d2a9a3bf502736749563e82974a5744dbb4942c1f2b2dff774594252" +} diff --git a/backend/.sqlx/query-273e18a8fe284da79b0929678f2e07d2db3240c1501d99e6b3d2fccfd7405d06.json b/backend/.sqlx/query-273e18a8fe284da79b0929678f2e07d2db3240c1501d99e6b3d2fccfd7405d06.json new file mode 100644 index 0000000000..026b4bdf0d --- /dev/null +++ b/backend/.sqlx/query-273e18a8fe284da79b0929678f2e07d2db3240c1501d99e6b3d2fccfd7405d06.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mcp_oauth_server_code SET user_email = $1 WHERE user_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "273e18a8fe284da79b0929678f2e07d2db3240c1501d99e6b3d2fccfd7405d06" +} diff --git a/backend/.sqlx/query-287a9f01a5b789b7724d7209f8f399638dd57f24fb407cbcb4fe4a510a74d3af.json b/backend/.sqlx/query-287a9f01a5b789b7724d7209f8f399638dd57f24fb407cbcb4fe4a510a74d3af.json new file mode 100644 index 0000000000..193a6ec42a --- /dev/null +++ b/backend/.sqlx/query-287a9f01a5b789b7724d7209f8f399638dd57f24fb407cbcb4fe4a510a74d3af.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE flow SET on_behalf_of_email = $1 WHERE on_behalf_of_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "287a9f01a5b789b7724d7209f8f399638dd57f24fb407cbcb4fe4a510a74d3af" +} diff --git a/backend/.sqlx/query-299a5d12779185a51b6e6815208dfe0aae64c39637208d081462b84d8636aa42.json b/backend/.sqlx/query-299a5d12779185a51b6e6815208dfe0aae64c39637208d081462b84d8636aa42.json new file mode 100644 index 0000000000..8935d39227 --- /dev/null +++ b/backend/.sqlx/query-299a5d12779185a51b6e6815208dfe0aae64c39637208d081462b84d8636aa42.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM workspace_invite o WHERE o.email = $1 AND EXISTS (SELECT 1 FROM workspace_invite n WHERE n.email = $2 AND n.workspace_id = o.workspace_id)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "299a5d12779185a51b6e6815208dfe0aae64c39637208d081462b84d8636aa42" +} diff --git a/backend/.sqlx/query-2b3ffacd09b0fa4bb458f18adbd7cb0f57455338d28e3f164a105a51510c1cc1.json b/backend/.sqlx/query-2b3ffacd09b0fa4bb458f18adbd7cb0f57455338d28e3f164a105a51510c1cc1.json new file mode 100644 index 0000000000..9188048a22 --- /dev/null +++ b/backend/.sqlx/query-2b3ffacd09b0fa4bb458f18adbd7cb0f57455338d28e3f164a105a51510c1cc1.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT owner FROM workspace WHERE id = 'test-workspace'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "owner", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "2b3ffacd09b0fa4bb458f18adbd7cb0f57455338d28e3f164a105a51510c1cc1" +} diff --git a/backend/.sqlx/query-2f74858ebe82499e06283b0d22cc19ecf4e8c9a9ea6a76977bb17aefe9eb8f27.json b/backend/.sqlx/query-2f74858ebe82499e06283b0d22cc19ecf4e8c9a9ea6a76977bb17aefe9eb8f27.json new file mode 100644 index 0000000000..0b61f2f930 --- /dev/null +++ b/backend/.sqlx/query-2f74858ebe82499e06283b0d22cc19ecf4e8c9a9ea6a76977bb17aefe9eb8f27.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM tutorial_progress WHERE email = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "2f74858ebe82499e06283b0d22cc19ecf4e8c9a9ea6a76977bb17aefe9eb8f27" +} diff --git a/backend/.sqlx/query-32e6b8ee457bcf28ed052f56ac8d24ad333ee5c75086ab1f07ed952cf6aecea9.json b/backend/.sqlx/query-32e6b8ee457bcf28ed052f56ac8d24ad333ee5c75086ab1f07ed952cf6aecea9.json new file mode 100644 index 0000000000..c40d60ddbe --- /dev/null +++ b/backend/.sqlx/query-32e6b8ee457bcf28ed052f56ac8d24ad333ee5c75086ab1f07ed952cf6aecea9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE http_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "32e6b8ee457bcf28ed052f56ac8d24ad333ee5c75086ab1f07ed952cf6aecea9" +} diff --git a/backend/.sqlx/query-34f1e04d3ff0ffe7fe4e142bb54e00431bc7fb4a4b20dacdce79316d7a84ba8f.json b/backend/.sqlx/query-34f1e04d3ff0ffe7fe4e142bb54e00431bc7fb4a4b20dacdce79316d7a84ba8f.json new file mode 100644 index 0000000000..7d28aad8ea --- /dev/null +++ b/backend/.sqlx/query-34f1e04d3ff0ffe7fe4e142bb54e00431bc7fb4a4b20dacdce79316d7a84ba8f.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM usage WHERE id = $1 AND NOT is_workspace", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "34f1e04d3ff0ffe7fe4e142bb54e00431bc7fb4a4b20dacdce79316d7a84ba8f" +} diff --git a/backend/.sqlx/query-35384f86bf1058a228307655c545bfbe91032050603e882bca0d7352a0f08a62.json b/backend/.sqlx/query-35384f86bf1058a228307655c545bfbe91032050603e882bca0d7352a0f08a62.json new file mode 100644 index 0000000000..a703ab8c9d --- /dev/null +++ b/backend/.sqlx/query-35384f86bf1058a228307655c545bfbe91032050603e882bca0d7352a0f08a62.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job SET permissioned_as_email = $1 WHERE permissioned_as_email = $2 AND id IN (SELECT id FROM v2_job_queue)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "35384f86bf1058a228307655c545bfbe91032050603e882bca0d7352a0f08a62" +} diff --git a/backend/.sqlx/query-35b7c715362239c09765d3717340216154b4bda0bd5729e93318fd6527fbbbc4.json b/backend/.sqlx/query-35b7c715362239c09765d3717340216154b4bda0bd5729e93318fd6527fbbbc4.json new file mode 100644 index 0000000000..5109912701 --- /dev/null +++ b/backend/.sqlx/query-35b7c715362239c09765d3717340216154b4bda0bd5729e93318fd6527fbbbc4.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE kafka_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "35b7c715362239c09765d3717340216154b4bda0bd5729e93318fd6527fbbbc4" +} diff --git a/backend/.sqlx/query-3b7e46f80cf1e543a76f90c74080a3e2d1cf04812aa1091e3c64f262988da099.json b/backend/.sqlx/query-3b7e46f80cf1e543a76f90c74080a3e2d1cf04812aa1091e3c64f262988da099.json new file mode 100644 index 0000000000..94b6083732 --- /dev/null +++ b/backend/.sqlx/query-3b7e46f80cf1e543a76f90c74080a3e2d1cf04812aa1091e3c64f262988da099.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT username FROM password WHERE email = $1 FOR UPDATE", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + true + ] + }, + "hash": "3b7e46f80cf1e543a76f90c74080a3e2d1cf04812aa1091e3c64f262988da099" +} diff --git a/backend/.sqlx/query-3c5e00d0e79fa518a95b05a00e75dbd96d7852ad340759252de4dcb57ad22a43.json b/backend/.sqlx/query-3c5e00d0e79fa518a95b05a00e75dbd96d7852ad340759252de4dcb57ad22a43.json new file mode 100644 index 0000000000..6cfca6e125 --- /dev/null +++ b/backend/.sqlx/query-3c5e00d0e79fa518a95b05a00e75dbd96d7852ad340759252de4dcb57ad22a43.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT username FROM password WHERE email = 'renamed@windmill.dev'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "username", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + true + ] + }, + "hash": "3c5e00d0e79fa518a95b05a00e75dbd96d7852ad340759252de4dcb57ad22a43" +} diff --git a/backend/.sqlx/query-3fec727489a5166c3036593d7089fd252e2c779762cb359f98a7d17e657a926f.json b/backend/.sqlx/query-3fec727489a5166c3036593d7089fd252e2c779762cb359f98a7d17e657a926f.json new file mode 100644 index 0000000000..8c1515f633 --- /dev/null +++ b/backend/.sqlx/query-3fec727489a5166c3036593d7089fd252e2c779762cb359f98a7d17e657a926f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_invite SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "3fec727489a5166c3036593d7089fd252e2c779762cb359f98a7d17e657a926f" +} diff --git a/backend/.sqlx/query-44356ac660ef1113f21b3ec24d67de7e64cda8f1e8419c231ecc090447614c4e.json b/backend/.sqlx/query-44356ac660ef1113f21b3ec24d67de7e64cda8f1e8419c231ecc090447614c4e.json new file mode 100644 index 0000000000..5d865507b9 --- /dev/null +++ b/backend/.sqlx/query-44356ac660ef1113f21b3ec24d67de7e64cda8f1e8419c231ecc090447614c4e.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE amqp_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "44356ac660ef1113f21b3ec24d67de7e64cda8f1e8419c231ecc090447614c4e" +} diff --git a/backend/.sqlx/query-5ae3532928770d6bd9fc57a3ca489f819d8845ddb54f09f53880a55cd2304c8c.json b/backend/.sqlx/query-5ae3532928770d6bd9fc57a3ca489f819d8845ddb54f09f53880a55cd2304c8c.json new file mode 100644 index 0000000000..739065fab5 --- /dev/null +++ b/backend/.sqlx/query-5ae3532928770d6bd9fc57a3ca489f819d8845ddb54f09f53880a55cd2304c8c.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT permissioned_as FROM schedule WHERE path = 'u/test-user-2/sched'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "permissioned_as", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "5ae3532928770d6bd9fc57a3ca489f819d8845ddb54f09f53880a55cd2304c8c" +} diff --git a/backend/.sqlx/query-5c3e4a9fd4ac5ce75326230ecbbd40bb126ae3e2dff1824904da91b2f7d900cd.json b/backend/.sqlx/query-5c3e4a9fd4ac5ce75326230ecbbd40bb126ae3e2dff1824904da91b2f7d900cd.json new file mode 100644 index 0000000000..d8f2889bdb --- /dev/null +++ b/backend/.sqlx/query-5c3e4a9fd4ac5ce75326230ecbbd40bb126ae3e2dff1824904da91b2f7d900cd.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mqtt_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "5c3e4a9fd4ac5ce75326230ecbbd40bb126ae3e2dff1824904da91b2f7d900cd" +} diff --git a/backend/.sqlx/query-5d1bcca26a78968351da909b23cb5fd0d90ed946263db6ffda99ca1cd9e727f9.json b/backend/.sqlx/query-5d1bcca26a78968351da909b23cb5fd0d90ed946263db6ffda99ca1cd9e727f9.json new file mode 100644 index 0000000000..0cf33d83dd --- /dev/null +++ b/backend/.sqlx/query-5d1bcca26a78968351da909b23cb5fd0d90ed946263db6ffda99ca1cd9e727f9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE usr SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "5d1bcca26a78968351da909b23cb5fd0d90ed946263db6ffda99ca1cd9e727f9" +} diff --git a/backend/.sqlx/query-5e74efd5f9152deb9511d2f1efc084cbf6aa8f252d86a50fa3a4f4e93bf373aa.json b/backend/.sqlx/query-5e74efd5f9152deb9511d2f1efc084cbf6aa8f252d86a50fa3a4f4e93bf373aa.json new file mode 100644 index 0000000000..7463702412 --- /dev/null +++ b/backend/.sqlx/query-5e74efd5f9152deb9511d2f1efc084cbf6aa8f252d86a50fa3a4f4e93bf373aa.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE tutorial_progress SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "5e74efd5f9152deb9511d2f1efc084cbf6aa8f252d86a50fa3a4f4e93bf373aa" +} diff --git a/backend/.sqlx/query-629a6f38411c49c13bfb7773cef87ef0b833f57e23392ba0f9c6d2a165a4c9fe.json b/backend/.sqlx/query-629a6f38411c49c13bfb7773cef87ef0b833f57e23392ba0f9c6d2a165a4c9fe.json new file mode 100644 index 0000000000..e4065a22a7 --- /dev/null +++ b/backend/.sqlx/query-629a6f38411c49c13bfb7773cef87ef0b833f57e23392ba0f9c6d2a165a4c9fe.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO app(workspace_id, path, summary, policy, versions)\n VALUES ('test-workspace', 'u/test-user-2/app', '', '{\"on_behalf_of\": \"test2@windmill.dev\", \"on_behalf_of_email\": \"test2@windmill.dev\"}'::jsonb, '{}')", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "629a6f38411c49c13bfb7773cef87ef0b833f57e23392ba0f9c6d2a165a4c9fe" +} diff --git a/backend/.sqlx/query-639f87dbe115bbd3d9eb9a70e74b4c8b3a71829aa9048fc2459e1f6603956e5d.json b/backend/.sqlx/query-639f87dbe115bbd3d9eb9a70e74b4c8b3a71829aa9048fc2459e1f6603956e5d.json new file mode 100644 index 0000000000..a86b2ba4b6 --- /dev/null +++ b/backend/.sqlx/query-639f87dbe115bbd3d9eb9a70e74b4c8b3a71829aa9048fc2459e1f6603956e5d.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE v2_job SET permissioned_as = $1 WHERE permissioned_as = $2 AND id IN (SELECT id FROM v2_job_queue)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "639f87dbe115bbd3d9eb9a70e74b4c8b3a71829aa9048fc2459e1f6603956e5d" +} diff --git a/backend/.sqlx/query-647c12e69360e6a34d01a563e6e84b26192c18cb4ede596233c00187b789d9ec.json b/backend/.sqlx/query-647c12e69360e6a34d01a563e6e84b26192c18cb4ede596233c00187b789d9ec.json new file mode 100644 index 0000000000..2b72dd883a --- /dev/null +++ b/backend/.sqlx/query-647c12e69360e6a34d01a563e6e84b26192c18cb4ede596233c00187b789d9ec.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE websocket_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "647c12e69360e6a34d01a563e6e84b26192c18cb4ede596233c00187b789d9ec" +} diff --git a/backend/.sqlx/query-65ee4ace29b39818594efdfa16453e4806c4dca776f80789a57d4d85e16aa197.json b/backend/.sqlx/query-65ee4ace29b39818594efdfa16453e4806c4dca776f80789a57d4d85e16aa197.json new file mode 100644 index 0000000000..5d557f14d9 --- /dev/null +++ b/backend/.sqlx/query-65ee4ace29b39818594efdfa16453e4806c4dca776f80789a57d4d85e16aa197.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE usage SET id = $1 WHERE id = $2 AND NOT is_workspace", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "65ee4ace29b39818594efdfa16453e4806c4dca776f80789a57d4d85e16aa197" +} diff --git a/backend/.sqlx/query-73910791d9eca97624c1b1b9dd65cc861d0339a4238bce6928af49601a6ad4d1.json b/backend/.sqlx/query-73910791d9eca97624c1b1b9dd65cc861d0339a4238bce6928af49601a6ad4d1.json new file mode 100644 index 0000000000..cbf6a2dfa5 --- /dev/null +++ b/backend/.sqlx/query-73910791d9eca97624c1b1b9dd65cc861d0339a4238bce6928af49601a6ad4d1.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_fork_deployment_request_assignee SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "73910791d9eca97624c1b1b9dd65cc861d0339a4238bce6928af49601a6ad4d1" +} diff --git a/backend/.sqlx/query-774db0fcbc3905a8c80f271612efcaf7268011f8868028cad6d5339419cb7aef.json b/backend/.sqlx/query-774db0fcbc3905a8c80f271612efcaf7268011f8868028cad6d5339419cb7aef.json new file mode 100644 index 0000000000..6817825419 --- /dev/null +++ b/backend/.sqlx/query-774db0fcbc3905a8c80f271612efcaf7268011f8868028cad6d5339419cb7aef.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE email_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "774db0fcbc3905a8c80f271612efcaf7268011f8868028cad6d5339419cb7aef" +} diff --git a/backend/.sqlx/query-883be05a1a02dde8d9056094a4f17491791832d5bd206ab99a8b96ad30a0a3f5.json b/backend/.sqlx/query-883be05a1a02dde8d9056094a4f17491791832d5bd206ab99a8b96ad30a0a3f5.json new file mode 100644 index 0000000000..870afd4671 --- /dev/null +++ b/backend/.sqlx/query-883be05a1a02dde8d9056094a4f17491791832d5bd206ab99a8b96ad30a0a3f5.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE password SET username = 'test-user-2' WHERE email = 'test2@windmill.dev'", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "883be05a1a02dde8d9056094a4f17491791832d5bd206ab99a8b96ad30a0a3f5" +} diff --git a/backend/.sqlx/query-8bd1c2f216b4c525888bb94f94e09c3c519eb4bccea78954cb25ff945807883c.json b/backend/.sqlx/query-8bd1c2f216b4c525888bb94f94e09c3c519eb4bccea78954cb25ff945807883c.json new file mode 100644 index 0000000000..ff3fb07cca --- /dev/null +++ b/backend/.sqlx/query-8bd1c2f216b4c525888bb94f94e09c3c519eb4bccea78954cb25ff945807883c.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM email_to_igroup o WHERE o.email = $1 AND EXISTS (SELECT 1 FROM email_to_igroup n WHERE n.email = $2 AND n.igroup = o.igroup)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "8bd1c2f216b4c525888bb94f94e09c3c519eb4bccea78954cb25ff945807883c" +} diff --git a/backend/.sqlx/query-9d6375303333eab6424dfba2b8fc4afb0d3fc87b79818b5087919a7ae604545f.json b/backend/.sqlx/query-9d6375303333eab6424dfba2b8fc4afb0d3fc87b79818b5087919a7ae604545f.json new file mode 100644 index 0000000000..b92ad418b7 --- /dev/null +++ b/backend/.sqlx/query-9d6375303333eab6424dfba2b8fc4afb0d3fc87b79818b5087919a7ae604545f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE gcp_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "9d6375303333eab6424dfba2b8fc4afb0d3fc87b79818b5087919a7ae604545f" +} diff --git a/backend/.sqlx/query-a3233c8a26f4ca47e957d55e77d9e3ccd51dcf9d259527e0b1f20d6e7f43f80b.json b/backend/.sqlx/query-a3233c8a26f4ca47e957d55e77d9e3ccd51dcf9d259527e0b1f20d6e7f43f80b.json new file mode 100644 index 0000000000..60f2c4ad34 --- /dev/null +++ b/backend/.sqlx/query-a3233c8a26f4ca47e957d55e77d9e3ccd51dcf9d259527e0b1f20d6e7f43f80b.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT COUNT(*) FROM password WHERE email = 'test2@windmill.dev'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "count", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "a3233c8a26f4ca47e957d55e77d9e3ccd51dcf9d259527e0b1f20d6e7f43f80b" +} diff --git a/backend/.sqlx/query-a718367f00f7dc0280ab297ff66a939f930eb2a43beac528fd3b5fb6add5e251.json b/backend/.sqlx/query-a718367f00f7dc0280ab297ff66a939f930eb2a43beac528fd3b5fb6add5e251.json new file mode 100644 index 0000000000..eedd656a9a --- /dev/null +++ b/backend/.sqlx/query-a718367f00f7dc0280ab297ff66a939f930eb2a43beac528fd3b5fb6add5e251.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO notify_event (channel, payload) SELECT 'notify_token_invalidation', token_prefix FROM token WHERE email = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "a718367f00f7dc0280ab297ff66a939f930eb2a43beac528fd3b5fb6add5e251" +} diff --git a/backend/.sqlx/query-ab07fa69343f1ab5e2c89911c314d027a05302085c838aa1c2d84406f9fffdb2.json b/backend/.sqlx/query-ab07fa69343f1ab5e2c89911c314d027a05302085c838aa1c2d84406f9fffdb2.json new file mode 100644 index 0000000000..1a0d20f9b6 --- /dev/null +++ b/backend/.sqlx/query-ab07fa69343f1ab5e2c89911c314d027a05302085c838aa1c2d84406f9fffdb2.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT policy::text FROM app WHERE path = 'u/test-user-2/app' AND workspace_id = 'test-workspace'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "policy", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "ab07fa69343f1ab5e2c89911c314d027a05302085c838aa1c2d84406f9fffdb2" +} diff --git a/backend/.sqlx/query-acf96371f08812a15760436f43f05290ec39617e18e65f05fa4a9b002560a6d5.json b/backend/.sqlx/query-acf96371f08812a15760436f43f05290ec39617e18e65f05fa4a9b002560a6d5.json new file mode 100644 index 0000000000..995c9ab14f --- /dev/null +++ b/backend/.sqlx/query-acf96371f08812a15760436f43f05290ec39617e18e65f05fa4a9b002560a6d5.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE script SET on_behalf_of_email = $1 WHERE on_behalf_of_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "acf96371f08812a15760436f43f05290ec39617e18e65f05fa4a9b002560a6d5" +} diff --git a/backend/.sqlx/query-aefab48bd35a3bbfdb6d5a86d017acdd2d3759e33b1c75e54aee140d8db562d6.json b/backend/.sqlx/query-aefab48bd35a3bbfdb6d5a86d017acdd2d3759e33b1c75e54aee140d8db562d6.json new file mode 100644 index 0000000000..8a27afe088 --- /dev/null +++ b/backend/.sqlx/query-aefab48bd35a3bbfdb6d5a86d017acdd2d3759e33b1c75e54aee140d8db562d6.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_fork_deployment_request SET requested_by_email = $1 WHERE requested_by_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "aefab48bd35a3bbfdb6d5a86d017acdd2d3759e33b1c75e54aee140d8db562d6" +} diff --git a/backend/.sqlx/query-c22b71a3a40710e44e66aa48bda41de76b2cc0d0495c7d32f565d2548bf8c860.json b/backend/.sqlx/query-c22b71a3a40710e44e66aa48bda41de76b2cc0d0495c7d32f565d2548bf8c860.json new file mode 100644 index 0000000000..1bea38eb84 --- /dev/null +++ b/backend/.sqlx/query-c22b71a3a40710e44e66aa48bda41de76b2cc0d0495c7d32f565d2548bf8c860.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT default_permissioned_as::text FROM folder WHERE workspace_id = 'test-workspace' AND name = 'fold'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "default_permissioned_as", + "type_info": "Text" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + null + ] + }, + "hash": "c22b71a3a40710e44e66aa48bda41de76b2cc0d0495c7d32f565d2548bf8c860" +} diff --git a/backend/.sqlx/query-c9513c08e03089a581a8bcc5178094fc42098ac26facbd010006ae06da284131.json b/backend/.sqlx/query-c9513c08e03089a581a8bcc5178094fc42098ac26facbd010006ae06da284131.json new file mode 100644 index 0000000000..26acaae551 --- /dev/null +++ b/backend/.sqlx/query-c9513c08e03089a581a8bcc5178094fc42098ac26facbd010006ae06da284131.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(\n SELECT 1 FROM workspace WHERE owner = $1\n UNION ALL SELECT 1 FROM workspace_settings WHERE slack_email = $1\n UNION ALL SELECT 1 FROM usage WHERE id = $1 AND NOT is_workspace\n UNION ALL SELECT 1 FROM v2_job WHERE permissioned_as = $1 AND id IN (SELECT id FROM v2_job_queue))", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "c9513c08e03089a581a8bcc5178094fc42098ac26facbd010006ae06da284131" +} diff --git a/backend/.sqlx/query-c9d78891f8ccdfc12c8315653187cc8d2d4a2df257e5fd723016c6344174d4a1.json b/backend/.sqlx/query-c9d78891f8ccdfc12c8315653187cc8d2d4a2df257e5fd723016c6344174d4a1.json new file mode 100644 index 0000000000..c7fef53db0 --- /dev/null +++ b/backend/.sqlx/query-c9d78891f8ccdfc12c8315653187cc8d2d4a2df257e5fd723016c6344174d4a1.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE nats_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "c9d78891f8ccdfc12c8315653187cc8d2d4a2df257e5fd723016c6344174d4a1" +} diff --git a/backend/.sqlx/query-d24faa96edde6ff6cb7c443775552419ff11b6671a58e3aa0c75a7b48973437a.json b/backend/.sqlx/query-d24faa96edde6ff6cb7c443775552419ff11b6671a58e3aa0c75a7b48973437a.json new file mode 100644 index 0000000000..0cb9a640b5 --- /dev/null +++ b/backend/.sqlx/query-d24faa96edde6ff6cb7c443775552419ff11b6671a58e3aa0c75a7b48973437a.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE sqs_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d24faa96edde6ff6cb7c443775552419ff11b6671a58e3aa0c75a7b48973437a" +} diff --git a/backend/.sqlx/query-d5f94950c15ee592d5580cf001b37560f2093f9569a2d8a4a702004041976961.json b/backend/.sqlx/query-d5f94950c15ee592d5580cf001b37560f2093f9569a2d8a4a702004041976961.json new file mode 100644 index 0000000000..fd2e29667c --- /dev/null +++ b/backend/.sqlx/query-d5f94950c15ee592d5580cf001b37560f2093f9569a2d8a4a702004041976961.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "DELETE FROM unique_ext_jwt_token WHERE email = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "d5f94950c15ee592d5580cf001b37560f2093f9569a2d8a4a702004041976961" +} diff --git a/backend/.sqlx/query-d7597022d6fc01601bff5f243fb38080fc6b65e95a3aad630c7abe3edcedbc98.json b/backend/.sqlx/query-d7597022d6fc01601bff5f243fb38080fc6b65e95a3aad630c7abe3edcedbc98.json new file mode 100644 index 0000000000..072e317008 --- /dev/null +++ b/backend/.sqlx/query-d7597022d6fc01601bff5f243fb38080fc6b65e95a3aad630c7abe3edcedbc98.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE token SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d7597022d6fc01601bff5f243fb38080fc6b65e95a3aad630c7abe3edcedbc98" +} diff --git a/backend/.sqlx/query-d94140411f0ddefd74732e9a755249a044397b6fbfe164bc3dfd007ccd490388.json b/backend/.sqlx/query-d94140411f0ddefd74732e9a755249a044397b6fbfe164bc3dfd007ccd490388.json new file mode 100644 index 0000000000..ef1532c103 --- /dev/null +++ b/backend/.sqlx/query-d94140411f0ddefd74732e9a755249a044397b6fbfe164bc3dfd007ccd490388.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace SET owner = 'test2@windmill.dev' WHERE id = 'test-workspace'", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "d94140411f0ddefd74732e9a755249a044397b6fbfe164bc3dfd007ccd490388" +} diff --git a/backend/.sqlx/query-d9b33bb1cf7894d5c61caa599975cc39b961191259bbccc233edca59bc7252b9.json b/backend/.sqlx/query-d9b33bb1cf7894d5c61caa599975cc39b961191259bbccc233edca59bc7252b9.json new file mode 100644 index 0000000000..ef8eb1982a --- /dev/null +++ b/backend/.sqlx/query-d9b33bb1cf7894d5c61caa599975cc39b961191259bbccc233edca59bc7252b9.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE azure_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d9b33bb1cf7894d5c61caa599975cc39b961191259bbccc233edca59bc7252b9" +} diff --git a/backend/.sqlx/query-d9b877b970b8e7e5d895d9b4f83ba945776b17c153b77cb2933864e9cd313439.json b/backend/.sqlx/query-d9b877b970b8e7e5d895d9b4f83ba945776b17c153b77cb2933864e9cd313439.json new file mode 100644 index 0000000000..69fe5d68df --- /dev/null +++ b/backend/.sqlx/query-d9b877b970b8e7e5d895d9b4f83ba945776b17c153b77cb2933864e9cd313439.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE job_perms SET email = $1 WHERE email = $2 AND job_id IN (SELECT id FROM v2_job_queue)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "d9b877b970b8e7e5d895d9b4f83ba945776b17c153b77cb2933864e9cd313439" +} diff --git a/backend/.sqlx/query-de902e3eded6c244ea4b4594c30e32b9e86b1902bfa504d60f0b3bef8f1e8ea6.json b/backend/.sqlx/query-de902e3eded6c244ea4b4594c30e32b9e86b1902bfa504d60f0b3bef8f1e8ea6.json new file mode 100644 index 0000000000..07e885bdb6 --- /dev/null +++ b/backend/.sqlx/query-de902e3eded6c244ea4b4594c30e32b9e86b1902bfa504d60f0b3bef8f1e8ea6.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE folder SET default_permissioned_as = (\n SELECT jsonb_agg(\n CASE WHEN rule->>'permissioned_as' = $2\n THEN jsonb_set(rule, ARRAY['permissioned_as'], to_jsonb($1::text))\n ELSE rule END\n ORDER BY ord)\n FROM jsonb_array_elements(default_permissioned_as) WITH ORDINALITY AS t(rule, ord))\n WHERE default_permissioned_as @> jsonb_build_array(jsonb_build_object('permissioned_as', $2::text))", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "de902e3eded6c244ea4b4594c30e32b9e86b1902bfa504d60f0b3bef8f1e8ea6" +} diff --git a/backend/.sqlx/query-df893ad32fd18b55d975247289d32fb80cbd8668c7c50c1f0b2ebd5faf50dce1.json b/backend/.sqlx/query-df893ad32fd18b55d975247289d32fb80cbd8668c7c50c1f0b2ebd5faf50dce1.json new file mode 100644 index 0000000000..ae3a1e200f --- /dev/null +++ b/backend/.sqlx/query-df893ad32fd18b55d975247289d32fb80cbd8668c7c50c1f0b2ebd5faf50dce1.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE password SET email = 'Legacy@windmill.dev' WHERE email = 'renamed@windmill.dev'", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "df893ad32fd18b55d975247289d32fb80cbd8668c7c50c1f0b2ebd5faf50dce1" +} diff --git a/backend/.sqlx/query-e084010d1ff7492b77156c01bdaebae14f8c39ba0715fc1000fb8ff1bf78169f.json b/backend/.sqlx/query-e084010d1ff7492b77156c01bdaebae14f8c39ba0715fc1000fb8ff1bf78169f.json new file mode 100644 index 0000000000..83676487a7 --- /dev/null +++ b/backend/.sqlx/query-e084010d1ff7492b77156c01bdaebae14f8c39ba0715fc1000fb8ff1bf78169f.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace SET owner = $1 WHERE owner = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "e084010d1ff7492b77156c01bdaebae14f8c39ba0715fc1000fb8ff1bf78169f" +} diff --git a/backend/.sqlx/query-ececc8132f985b3e2d8301d5dc24356953e6e6cd71f28384b6f51231a35c7947.json b/backend/.sqlx/query-ececc8132f985b3e2d8301d5dc24356953e6e6cd71f28384b6f51231a35c7947.json new file mode 100644 index 0000000000..51f7848a28 --- /dev/null +++ b/backend/.sqlx/query-ececc8132f985b3e2d8301d5dc24356953e6e6cd71f28384b6f51231a35c7947.json @@ -0,0 +1,23 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT EXISTS(\n SELECT 1 FROM password WHERE lower(email) = $1 AND email <> $2\n UNION ALL SELECT 1 FROM usr WHERE lower(email) = $1 AND email <> $2)", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "exists", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + null + ] + }, + "hash": "ececc8132f985b3e2d8301d5dc24356953e6e6cd71f28384b6f51231a35c7947" +} diff --git a/backend/.sqlx/query-eeb0ef2be5db28fa722a977dfbecfe0dddc1d33b3f98c649cd91d9a3a5785e34.json b/backend/.sqlx/query-eeb0ef2be5db28fa722a977dfbecfe0dddc1d33b3f98c649cd91d9a3a5785e34.json new file mode 100644 index 0000000000..7577ef485a --- /dev/null +++ b/backend/.sqlx/query-eeb0ef2be5db28fa722a977dfbecfe0dddc1d33b3f98c649cd91d9a3a5785e34.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE capture_config SET email = $1 WHERE email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "eeb0ef2be5db28fa722a977dfbecfe0dddc1d33b3f98c649cd91d9a3a5785e34" +} diff --git a/backend/.sqlx/query-eff07725675fa97c91f86564c13658316f5c3dff23704feca654dedc035adacf.json b/backend/.sqlx/query-eff07725675fa97c91f86564c13658316f5c3dff23704feca654dedc035adacf.json new file mode 100644 index 0000000000..e0ea48787d --- /dev/null +++ b/backend/.sqlx/query-eff07725675fa97c91f86564c13658316f5c3dff23704feca654dedc035adacf.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE mcp_oauth_refresh_token SET user_email = $1 WHERE user_email = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "eff07725675fa97c91f86564c13658316f5c3dff23704feca654dedc035adacf" +} diff --git a/backend/.sqlx/query-f8c3c8cc0b3bb90adf611bab39f71cdd9a96e35508c852ab9c1dcbae3decbaba.json b/backend/.sqlx/query-f8c3c8cc0b3bb90adf611bab39f71cdd9a96e35508c852ab9c1dcbae3decbaba.json new file mode 100644 index 0000000000..2c79b75f3e --- /dev/null +++ b/backend/.sqlx/query-f8c3c8cc0b3bb90adf611bab39f71cdd9a96e35508c852ab9c1dcbae3decbaba.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE app SET policy = jsonb_set(policy, ARRAY['on_behalf_of_email'], to_jsonb($1::text)) WHERE policy->>'on_behalf_of_email' = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f8c3c8cc0b3bb90adf611bab39f71cdd9a96e35508c852ab9c1dcbae3decbaba" +} diff --git a/backend/.sqlx/query-f8fa9ba552697292752e80a224e9c85bbed7cb0196c40927bf5a542416cf3b15.json b/backend/.sqlx/query-f8fa9ba552697292752e80a224e9c85bbed7cb0196c40927bf5a542416cf3b15.json new file mode 100644 index 0000000000..e515a1d1b1 --- /dev/null +++ b/backend/.sqlx/query-f8fa9ba552697292752e80a224e9c85bbed7cb0196c40927bf5a542416cf3b15.json @@ -0,0 +1,12 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO schedule(workspace_id, path, edited_by, schedule, timezone, enabled, script_path, is_flow, args, email, permissioned_as)\n VALUES ('test-workspace', 'u/test-user-2/sched', 'test-user-2', '0 0 1 1 *', 'UTC', false, 'u/test-user-2/s', false, '{}'::json, 'test2@windmill.dev', 'test2@windmill.dev')", + "describe": { + "columns": [], + "parameters": { + "Left": [] + }, + "nullable": [] + }, + "hash": "f8fa9ba552697292752e80a224e9c85bbed7cb0196c40927bf5a542416cf3b15" +} diff --git a/backend/.sqlx/query-fbd24c0bc6c364d643f81f0a1baa4db6beaa12b133932ce0fd6c16a771a5a594.json b/backend/.sqlx/query-fbd24c0bc6c364d643f81f0a1baa4db6beaa12b133932ce0fd6c16a771a5a594.json new file mode 100644 index 0000000000..673e7cf2d7 --- /dev/null +++ b/backend/.sqlx/query-fbd24c0bc6c364d643f81f0a1baa4db6beaa12b133932ce0fd6c16a771a5a594.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE schedule SET permissioned_as = $1 WHERE permissioned_as = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Text" + ] + }, + "nullable": [] + }, + "hash": "fbd24c0bc6c364d643f81f0a1baa4db6beaa12b133932ce0fd6c16a771a5a594" +} diff --git a/backend/windmill-api-auth/src/auth.rs b/backend/windmill-api-auth/src/auth.rs index 59d9cd80b3..5219b41e9b 100644 --- a/backend/windmill-api-auth/src/auth.rs +++ b/backend/windmill-api-auth/src/auth.rs @@ -38,15 +38,21 @@ lazy_static::lazy_static! { // Global auth cache accessible from main.rs for direct invalidation pub static ref AUTH_CACHE: Cache<(String, String), ExpiringAuthCache> = Cache::new(300); // Cache for token -> email lookups (for non-workspace-member authenticated users) - static ref TOKEN_EMAIL_CACHE: Cache> = Cache::new(500); + static ref TOKEN_EMAIL_CACHE: Cache, std::time::Instant)> = Cache::new(500); } +/// A token keeps its identity when a superadmin moves the account to another address, so entries +/// here must expire on their own; nothing invalidates them by token hash. +const TOKEN_EMAIL_CACHE_TTL_SECS: u64 = 60; + /// Get email from a valid token, with caching. /// Used for WM_END_USER_EMAIL when user is authenticated but not a workspace member. async fn get_email_from_token(db: &DB, token: &str) -> Option { let t_hash = hash_token(token); - if let Some(cached) = TOKEN_EMAIL_CACHE.get(&t_hash) { - return cached; + if let Some((cached, cached_at)) = TOKEN_EMAIL_CACHE.get(&t_hash) { + if cached_at.elapsed().as_secs() < TOKEN_EMAIL_CACHE_TTL_SECS { + return cached; + } } let email = sqlx::query_scalar!( @@ -59,7 +65,7 @@ async fn get_email_from_token(db: &DB, token: &str) -> Option { .flatten() .flatten(); // email column is nullable, so we get Option> - TOKEN_EMAIL_CACHE.insert(t_hash, email.clone()); + TOKEN_EMAIL_CACHE.insert(t_hash, (email.clone(), std::time::Instant::now())); email } diff --git a/backend/windmill-api-integration-tests/tests/users.rs b/backend/windmill-api-integration-tests/tests/users.rs index db711c24e0..7b3457cde1 100644 --- a/backend/windmill-api-integration-tests/tests/users.rs +++ b/backend/windmill-api-integration-tests/tests/users.rs @@ -511,3 +511,152 @@ async fn test_user_endpoints(db: Pool) -> anyhow::Result<()> { Ok(()) } + +#[sqlx::test(migrations = "../migrations", fixtures("base"))] +async fn test_change_user_email(db: Pool) -> anyhow::Result<()> { + initialize_tracing().await; + let server = ApiServer::start(db.clone()).await?; + let port = server.addr.port(); + let global_base = format!("http://localhost:{port}/api/users"); + + let change_email = |email: &str, new_email: &str| { + authed(client().post(format!("{global_base}/change_email/{email}"))) + .json(&json!({ "new_email": new_email })) + .send() + }; + + sqlx::query!("UPDATE password SET username = 'test-user-2' WHERE email = 'test2@windmill.dev'") + .execute(&db) + .await?; + sqlx::query!("UPDATE workspace SET owner = 'test2@windmill.dev' WHERE id = 'test-workspace'") + .execute(&db) + .await?; + // A user whose username is their email is stored as the bare address in `permissioned_as` and + // in an app's policy, rather than as `u/{username}`. + sqlx::query!( + "INSERT INTO schedule(workspace_id, path, edited_by, schedule, timezone, enabled, script_path, is_flow, args, email, permissioned_as) + VALUES ('test-workspace', 'u/test-user-2/sched', 'test-user-2', '0 0 1 1 *', 'UTC', false, 'u/test-user-2/s', false, '{}'::json, 'test2@windmill.dev', 'test2@windmill.dev')" + ) + .execute(&db) + .await?; + sqlx::query!( + "INSERT INTO app(workspace_id, path, summary, policy, versions) + VALUES ('test-workspace', 'u/test-user-2/app', '', '{\"on_behalf_of\": \"test2@windmill.dev\", \"on_behalf_of_email\": \"test2@windmill.dev\"}'::jsonb, '{}')" + ) + .execute(&db) + .await?; + sqlx::query!( + "INSERT INTO folder(workspace_id, name, display_name, owners, extra_perms, default_permissioned_as) + VALUES ('test-workspace', 'fold', 'fold', '{}', '{}'::jsonb, + '[{\"path_glob\": \"a/**\", \"permissioned_as\": \"u/other\"}, {\"path_glob\": \"**\", \"permissioned_as\": \"test2@windmill.dev\"}]'::jsonb)" + ) + .execute(&db) + .await?; + + let resp = change_email("test2@windmill.dev", "renamed@windmill.dev") + .await + .unwrap(); + assert_eq!(resp.status(), 200, "change_email: {}", resp.text().await?); + + // The account row is moved, not recreated, so the instance-wide username and the workspace + // membership follow the new address. + let username = + sqlx::query_scalar!("SELECT username FROM password WHERE email = 'renamed@windmill.dev'") + .fetch_one(&db) + .await?; + assert_eq!(username.as_deref(), Some("test-user-2")); + + let workspaces = + sqlx::query_scalar!("SELECT workspace_id FROM usr WHERE email = 'renamed@windmill.dev'") + .fetch_all(&db) + .await?; + assert_eq!(workspaces, vec!["test-workspace".to_string()]); + + let owner = sqlx::query_scalar!("SELECT owner FROM workspace WHERE id = 'test-workspace'") + .fetch_one(&db) + .await?; + assert_eq!(owner, "renamed@windmill.dev"); + + // A `permissioned_as` (or app policy) holding the bare address is the sole identity reference + // those rows have: left stale, the schedule tick and the deployed app run as an account that no + // longer exists. + let permissioned_as = sqlx::query_scalar!( + "SELECT permissioned_as FROM schedule WHERE path = 'u/test-user-2/sched'" + ) + .fetch_one(&db) + .await?; + assert_eq!(permissioned_as, "renamed@windmill.dev"); + + let policy = sqlx::query_scalar!( + "SELECT policy::text FROM app WHERE path = 'u/test-user-2/app' AND workspace_id = 'test-workspace'" + ) + .fetch_one(&db) + .await? + .unwrap_or_default(); + assert!( + !policy.contains("test2@windmill.dev") && policy.contains("renamed@windmill.dev"), + "app policy should carry only the new address: {policy}" + ); + + // The rules keep their order, since the folder resolver takes the first glob that matches. + let rules = sqlx::query_scalar!( + "SELECT default_permissioned_as::text FROM folder WHERE workspace_id = 'test-workspace' AND name = 'fold'" + ) + .fetch_one(&db) + .await? + .unwrap_or_default(); + let rules: serde_json::Value = serde_json::from_str(&rules)?; + assert_eq!(rules[0]["permissioned_as"], "u/other"); + assert_eq!(rules[1]["permissioned_as"], "renamed@windmill.dev"); + + let old_rows = + sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE email = 'test2@windmill.dev'") + .fetch_one(&db) + .await?; + assert_eq!(old_rows, Some(0)); + + // Moving onto an address that already has an account would merge two identities. Login + // lowercases what it is given, so a case-only difference collides just the same. + let resp = change_email("test3@windmill.dev", "renamed@windmill.dev") + .await + .unwrap(); + assert_eq!(resp.status(), 400); + + sqlx::query!( + "UPDATE password SET email = 'Legacy@windmill.dev' WHERE email = 'renamed@windmill.dev'" + ) + .execute(&db) + .await?; + let resp = change_email("test3@windmill.dev", "legacy@windmill.dev") + .await + .unwrap(); + assert_eq!(resp.status(), 400); + + let resp = change_email("test3@windmill.dev", "not-an-email") + .await + .unwrap(); + assert_eq!(resp.status(), 400); + + let resp = change_email("nobody@windmill.dev", "somebody@windmill.dev") + .await + .unwrap(); + assert_eq!(resp.status(), 404); + + // Moving your own account would leave your cached identity pointing at a deleted address. + let resp = change_email("test@windmill.dev", "self@windmill.dev") + .await + .unwrap(); + assert_eq!(resp.status(), 400); + + // Only super admins may move an account. + let resp = client() + .post(format!("{global_base}/change_email/test3@windmill.dev")) + .header("Authorization", "Bearer SECRET_TOKEN_3") + .json(&json!({ "new_email": "hijacked@windmill.dev" })) + .send() + .await + .unwrap(); + assert_eq!(resp.status(), 401); + + Ok(()) +} diff --git a/backend/windmill-api-users/src/users.rs b/backend/windmill-api-users/src/users.rs index 0e22f4a09d..f78a9e85ff 100644 --- a/backend/windmill-api-users/src/users.rs +++ b/backend/windmill-api-users/src/users.rs @@ -48,6 +48,9 @@ use windmill_common::global_settings::AUTOMATE_USERNAME_CREATION_SETTING; use windmill_common::oauth2::InstanceEvent; use windmill_common::users::truncate_token; use windmill_common::users::COOKIE_NAME; +use windmill_common::users::{ + SUPERADMIN_NOTIFICATION_EMAIL, SUPERADMIN_SECRET_EMAIL, SUPERADMIN_SYNC_EMAIL, VALID_EMAIL, +}; use windmill_common::utils::paginate; use windmill_common::worker::CLOUD_HOSTED; use windmill_common::{ @@ -133,6 +136,7 @@ pub fn global_service() -> Router { .route("/update/{user}", post(update_user)) .route("/delete/{user}", delete(delete_user)) .route("/username_info/{user}", get(get_instance_username_info)) + .route("/change_email/{user}", post(change_user_email)) .route("/tokens/create", post(create_token)) .route("/tokens/delete/{token_prefix}", delete(delete_token)) .route( @@ -1664,6 +1668,529 @@ async fn delete_user( Ok(format!("email {} deleted", &email_to_delete)) } +#[derive(Deserialize)] +struct ChangeUserEmail { + new_email: String, +} + +/// `workspace.owner`, `workspace_settings.slack_email` and `usage.id` hold an email in a +/// `varchar(50)`, and `v2_job.permissioned_as` in a `varchar(55)`; every other email column is +/// `varchar(255)`. The strictest of the two bounds is used for all of them. +const SHORT_EMAIL_COLUMN_MAX_LEN: usize = 50; +const EMAIL_COLUMN_MAX_LEN: usize = 255; + +/// Move an account to a new email address, in place: the `password` row (and with it the +/// instance-wide username, the role and the login type) is kept and every email-keyed row is +/// repointed at the new address. +/// +/// `audit` is deliberately left alone: it records who did what at the time, so rewriting it +/// would falsify history. +async fn change_user_email( + authed: ApiAuthed, + OptJobAuthed { job_id, .. }: OptJobAuthed, + Path(old_email): Path, + Extension(db): Extension, + Json(ce): Json, +) -> Result { + require_super_admin(&db, &authed.email).await?; + forbid_superadmin_job_token(&db, &authed.email, job_id).await?; + + // The target is matched verbatim (accounts predating email normalization can hold uppercase), + // while the new address is normalized the same way account creation and login do. + let old_email = old_email.trim().to_string(); + let new_email = ce.new_email.trim().to_lowercase(); + + if !VALID_EMAIL.is_match(&new_email) || new_email.len() > EMAIL_COLUMN_MAX_LEN { + return Err(Error::BadRequest(format!( + "{new_email} is not a valid email address of at most {EMAIL_COLUMN_MAX_LEN} characters" + ))); + } + + if new_email == old_email { + return Err(Error::BadRequest( + "The new email is identical to the current one".to_string(), + )); + } + + // Every API server caches the caller's identity behind their token and only drops it when the + // invalidation event is polled, so moving your own account would leave you authenticating as an + // address that no longer exists for a few seconds. + if old_email.eq_ignore_ascii_case(&authed.email) { + return Err(Error::BadRequest( + "You cannot change your own email, ask another superadmin to do it".to_string(), + )); + } + + for reserved in [ + SUPERADMIN_SECRET_EMAIL, + SUPERADMIN_NOTIFICATION_EMAIL, + SUPERADMIN_SYNC_EMAIL, + ] { + if old_email == reserved || new_email == reserved { + return Err(Error::BadRequest(format!( + "{reserved} is a reserved email address" + ))); + } + } + + let mut tx = db.begin().await?; + + // FOR UPDATE serializes concurrent moves of *this* account. Two moves of different accounts + // onto the same destination are stopped by the `password` primary key instead, which is why the + // unique violation below is mapped back onto the same 400 as the conflict check. + let username = sqlx::query_scalar!( + "SELECT username FROM password WHERE email = $1 FOR UPDATE", + &old_email + ) + .fetch_optional(&mut *tx) + .await?; + let username = not_found_if_none(username, "user", &old_email)?; + + // Compared case-insensitively: login lowercases what it is given, so an account stored with + // uppercase would be shadowed by a lowercase twin rather than collide with it. The moved + // account is excluded so that normalizing its own address to lowercase stays allowed. + let taken = sqlx::query_scalar!( + "SELECT EXISTS( + SELECT 1 FROM password WHERE lower(email) = $1 AND email <> $2 + UNION ALL SELECT 1 FROM usr WHERE lower(email) = $1 AND email <> $2)", + &new_email, + &old_email + ) + .fetch_one(&mut *tx) + .await? + .unwrap_or(false); + + if taken { + return Err(Error::BadRequest(format!( + "{new_email} is already used by another account" + ))); + } + + if new_email.len() > SHORT_EMAIL_COLUMN_MAX_LEN { + let referenced_by_short_column = sqlx::query_scalar!( + "SELECT EXISTS( + SELECT 1 FROM workspace WHERE owner = $1 + UNION ALL SELECT 1 FROM workspace_settings WHERE slack_email = $1 + UNION ALL SELECT 1 FROM usage WHERE id = $1 AND NOT is_workspace + UNION ALL SELECT 1 FROM v2_job WHERE permissioned_as = $1 AND id IN (SELECT id FROM v2_job_queue))", + &old_email + ) + .fetch_one(&mut *tx) + .await? + .unwrap_or(false); + if referenced_by_short_column { + return Err(Error::BadRequest(format!( + "{new_email} is longer than {SHORT_EMAIL_COLUMN_MAX_LEN} characters and this user owns a workspace, a Slack connection, usage counters or a queued job, whose columns cannot hold it" + ))); + } + } + + // A pending_user row only reserves a username for an address that has no account yet, which + // stops being true here. The moved account keeps its own username. + sqlx::query!("DELETE FROM pending_user WHERE email = $1", &new_email) + .execute(&mut *tx) + .await?; + + // ---- account ---- (draft.email follows through its ON UPDATE CASCADE fkey) + sqlx::query!( + "UPDATE password SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await + .map_err(|e| match &e { + sqlx::Error::Database(db_err) if db_err.is_unique_violation() => { + Error::BadRequest(format!("{new_email} is already used by another account")) + } + _ => e.into(), + })?; + + sqlx::query!( + "UPDATE usr SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE workspace SET owner = $1 WHERE owner = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // Slack commands run as this address when the command maps to no workspace user. + sqlx::query!( + "UPDATE workspace_settings SET slack_email = $1 WHERE slack_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // Per-user monthly execution counters, keyed by the email. + sqlx::query!( + "DELETE FROM usage WHERE id = $1 AND NOT is_workspace", + &new_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE usage SET id = $1 WHERE id = $2 AND NOT is_workspace", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // ---- instance groups and invites ---- (both keyed on the email, so drop the rows that would + // collide with what the new address was already granted before merging the old ones in) + sqlx::query!( + "DELETE FROM email_to_igroup o WHERE o.email = $1 AND EXISTS (SELECT 1 FROM email_to_igroup n WHERE n.email = $2 AND n.igroup = o.igroup)", + &old_email, + &new_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE email_to_igroup SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "DELETE FROM workspace_invite o WHERE o.email = $1 AND EXISTS (SELECT 1 FROM workspace_invite n WHERE n.email = $2 AND n.workspace_id = o.workspace_id)", + &old_email, + &new_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE workspace_invite SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!("DELETE FROM tutorial_progress WHERE email = $1", &new_email) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE tutorial_progress SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // ---- credentials ---- (a password reset link was mailed to the old address) + sqlx::query!("DELETE FROM magic_link WHERE email = $1", &old_email) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE token SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // Tokens stay valid, but every API server caches the authed user behind the raw token, so ask + // them all to drop those entries rather than serve the previous address until they expire. + sqlx::query!( + "INSERT INTO notify_event (channel, payload) SELECT 'notify_token_invalidation', token_prefix FROM token WHERE email = $1", + &new_email + ) + .execute(&mut *tx) + .await?; + + // An external JWT still asserts the old address, so its cached mapping is stale. + sqlx::query!( + "DELETE FROM unique_ext_jwt_token WHERE email = $1", + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE mcp_oauth_refresh_token SET user_email = $1 WHERE user_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE mcp_oauth_server_code SET user_email = $1 WHERE user_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // ---- runnables run on behalf of the user ---- + sqlx::query!( + "UPDATE schedule SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE capture_config SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE azure_trigger SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE script SET on_behalf_of_email = $1 WHERE on_behalf_of_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE flow SET on_behalf_of_email = $1 WHERE on_behalf_of_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // Apps carry the same identity inside their policy JSONB. An app running in Anonymous or + // Publisher mode takes its permissions from there rather than from the caller, so a stale + // address silently costs it its superadmin flag and its instance groups. + sqlx::query!( + "UPDATE app SET policy = jsonb_set(policy, ARRAY['on_behalf_of_email'], to_jsonb($1::text)) WHERE policy->>'on_behalf_of_email' = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // ---- permissioned_as holding a raw email ---- + // `username_to_permissioned_as` returns its input verbatim when it contains '@', and the + // migration that introduced these columns back-filled them the same way, so a user whose + // username is their email is stored as the bare address instead of `u/{username}`. Those rows + // are the ones that go stale here; `u/{username}` rows are safe because the username is kept. + sqlx::query!( + "UPDATE app SET policy = jsonb_set(policy, ARRAY['on_behalf_of'], to_jsonb($1::text)) WHERE policy->>'on_behalf_of' = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // A folder's default rules are an ordered array, first match wins, so the rewrite has to + // preserve their order. A rule left on the old address makes `ensure_permissioned_as_exists` + // reject the creation of every runnable the rule matches. + sqlx::query!( + r#"UPDATE folder SET default_permissioned_as = ( + SELECT jsonb_agg( + CASE WHEN rule->>'permissioned_as' = $2 + THEN jsonb_set(rule, ARRAY['permissioned_as'], to_jsonb($1::text)) + ELSE rule END + ORDER BY ord) + FROM jsonb_array_elements(default_permissioned_as) WITH ORDINALITY AS t(rule, ord)) + WHERE default_permissioned_as @> jsonb_build_array(jsonb_build_object('permissioned_as', $2::text))"#, + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE schedule SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE http_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE websocket_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE postgres_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE mqtt_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE kafka_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE nats_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE sqs_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE gcp_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE email_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE amqp_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE azure_trigger SET permissioned_as = $1 WHERE permissioned_as = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // ---- jobs ---- Restricted to what is still queued: those rows drive the permissions of a run + // that has not finished yet, whereas completed jobs are history and neither column is indexed + // (rewriting every past row of a busy user would hold this transaction's locks for minutes). + sqlx::query!( + "UPDATE v2_job SET permissioned_as_email = $1 WHERE permissioned_as_email = $2 AND id IN (SELECT id FROM v2_job_queue)", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE v2_job SET permissioned_as = $1 WHERE permissioned_as = $2 AND id IN (SELECT id FROM v2_job_queue)", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE job_perms SET email = $1 WHERE email = $2 AND job_id IN (SELECT id FROM v2_job_queue)", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + // ---- fork deployment requests ---- + sqlx::query!( + "UPDATE workspace_fork_deployment_request SET requested_by_email = $1 WHERE requested_by_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE workspace_fork_deployment_request_assignee SET email = $1 WHERE email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + sqlx::query!( + "UPDATE workspace_fork_deployment_request_comment SET author_email = $1 WHERE author_email = $2", + &new_email, + &old_email + ) + .execute(&mut *tx) + .await?; + + audit_log( + &mut *tx, + &authed, + "users.change_email", + ActionKind::Update, + "global", + Some(&old_email), + Some([("new_email", new_email.as_str())].into()), + ) + .await?; + + tx.commit().await?; + + Ok(format!( + "changed email of user {old_email} to {new_email}{}", + username + .map(|u| format!(", keeping the instance username {u}")) + .unwrap_or_default() + )) +} + lazy_static::lazy_static! { pub static ref NEW_USER_WEBHOOK: Option = std::env::var("NEW_USER_WEBHOOK").ok(); diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 15bc74920c..9c26d77392 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -732,6 +732,38 @@ paths: schema: type: string + /users/change_email/{email}: + post: + summary: global change user email (require super admin) + operationId: globalUserChangeEmail + tags: + - user + parameters: + - name: email + in: path + required: true + schema: + type: string + requestBody: + description: new email + required: true + content: + application/json: + schema: + type: object + properties: + new_email: + type: string + required: + - new_email + responses: + "200": + description: user email changed + content: + text/plain: + schema: + type: string + /users/delete/{email}: delete: summary: global delete user (require super admin) diff --git a/backend/windmill-common/src/users.rs b/backend/windmill-common/src/users.rs index 738dc84d14..1837e4dc4b 100644 --- a/backend/windmill-common/src/users.rs +++ b/backend/windmill-common/src/users.rs @@ -6,6 +6,15 @@ * LICENSE-AGPL for a copy of the license. */ +lazy_static::lazy_static! { + /// Matches the non-quoted, non-IP-literal subset of the `proper_email` CHECK constraint + /// carried by `usr` and `workspace_invite`, so anything accepted here is accepted by those + /// tables too. + pub static ref VALID_EMAIL: regex::Regex = regex::Regex::new( + r"^[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[A-Za-z0-9!#$%&'*+/=?^_`{|}~-]+)*@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?$" + ).unwrap(); +} + pub const SUPERADMIN_SECRET_EMAIL: &str = "superadmin_secret@windmill.dev"; pub const SUPERADMIN_NOTIFICATION_EMAIL: &str = "superadmin_notification@windmill.dev"; pub const SUPERADMIN_SYNC_EMAIL: &str = "superadmin_sync@windmill.dev"; @@ -167,6 +176,27 @@ mod tests { assert_eq!(username_to_permissioned_as("group-my-team"), "g/my-team"); } + #[test] + fn test_valid_email() { + for email in [ + "alice@example.com", + "alice.bob+tag@sub.example.co.uk", + "a_b-c!#$%&'*+/=?^_`{|}~@example.com", + ] { + assert!(VALID_EMAIL.is_match(email), "{email} should be valid"); + } + for email in [ + "alice", + "alice@example", + "alice@@example.com", + "alice @example.com", + "alice@example.com\nbob@example.com", + "", + ] { + assert!(!VALID_EMAIL.is_match(email), "{email} should be invalid"); + } + } + #[test] fn test_compute_highest_workspace_role_admin_wins() { let user_groups = vec!["ops".to_string(), "admins".to_string()]; diff --git a/frontend/src/lib/components/ChangeInstanceEmailInner.svelte b/frontend/src/lib/components/ChangeInstanceEmailInner.svelte new file mode 100644 index 0000000000..704908afa4 --- /dev/null +++ b/frontend/src/lib/components/ChangeInstanceEmailInner.svelte @@ -0,0 +1,101 @@ + + +
+ Email + { + e.stopPropagation() + }, + onkeydown: (e) => { + e.stopPropagation() + }, + onkeypress: (e) => { + e.stopPropagation() + if (e.key === 'Enter') { + changeEmail() + } + } + }} + bind:value={() => editedEmail ?? email, (v) => (editedEmail = String(v))} + /> + + {#if isSelf} +
+ You cannot change your own email — ask another superadmin. +
+ {/if} + + {#if changed && !isSelf} + + Changing the email of an existing account is a last resort. Prefer it only when the address + itself has to change and the account must be kept. +
+
+ The account keeps its instance-wide username{username ? ` (${username})` : ''}, role, + workspace memberships, drafts and tokens. But past runs and audit logs keep the previous + email, pending password reset links stop working, and the account inherits any instance group + membership or workspace invite already addressed to the new email — including one that grants + a role. +
+
+ If this user signs in through SSO or is managed by SCIM, update the identity provider first: otherwise + their next login recreates the previous email as a separate, empty account. +
+ {/if} + + +
diff --git a/frontend/src/lib/components/InstanceNameEditor.svelte b/frontend/src/lib/components/InstanceNameEditor.svelte index cb710cddce..3b5cce4945 100644 --- a/frontend/src/lib/components/InstanceNameEditor.svelte +++ b/frontend/src/lib/components/InstanceNameEditor.svelte @@ -8,6 +8,7 @@ import Popover from './meltComponents/Popover.svelte' import { offset, flip, shift } from 'svelte-floating-ui/dom' import ChangeInstanceUsernameInner from './ChangeInstanceUsernameInner.svelte' + import ChangeInstanceEmailInner from './ChangeInstanceEmailInner.svelte' import { UserService } from '$lib/gen' import { sendUserToast } from '$lib/toast' import TextInput from './text_input/TextInput.svelte' @@ -66,6 +67,12 @@ {/snippet} {#snippet content()}
+ dispatch('refresh')} + /> {#if automateUsernameCreation && username} {/if}