From efb4a27d5181bf9db3deb5e8100ec60adbe45e7f Mon Sep 17 00:00:00 2001 From: hugocasa Date: Fri, 20 Mar 2026 17:28:38 +0100 Subject: [PATCH] fix: replace email with permissioned_as for triggers/schedules (#8439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: replace email with permissioned_as for triggers/schedules Add a new `permissioned_as` column (format: `u/{username}`, `g/{group}`, or raw email) to all trigger tables and schedule. This value is used directly for job permission checks, removing the need for email lookups when creating/updating triggers. - Migration: add permissioned_as to all 9 trigger tables + schedule, drop email from trigger tables (schedule keeps it for backwards compat) - Backend: resolve_email() (async, DB) -> resolve_permissioned_as() (sync) - Email cache: get_email_from_permissioned_as() with quick_cache for places that still need email (fetch_api_authed, schedule backwards compat) - Frontend: rename email/preserve_email -> permissioned_as/preserve_permissioned_as in deploy data and OpenAPI schemas - Tests updated for new field names and u/{username} format Co-Authored-By: Claude Opus 4.6 (1M context) * fix sqlx/build * update ee ref * refactor: simplify resolve_edited_by to always use authed username Co-Authored-By: Claude Opus 4.6 (1M context) * fix compile + migration * update ref * test: add trigger trait method tests for permissioned_as queries Add tests that call TriggerCrud and Listener trait methods directly to verify dynamic SQL correctly references the permissioned_as column. Covers get_trigger_by_path, list_triggers, set_trigger_mode, and fetch_enabled_unlistened_triggers for all trigger types. Co-Authored-By: Claude Opus 4.6 (1M context) * update sqlx * fix: use permissioned_as directly for schedules and fix audit RLS for groups - Schedule: permissioned_as only set on create, not on edit/set_enabled - Schedule: stop reading email column, use get_email_from_permissioned_as - Triggers: use fetch_api_authed_from_permissioned_as instead of edited_by - Triggers: rename listener fields for clarity (username -> edited_by) - Fix audit author username for group permissioned_as (g/test -> group-test) to match session.user, preventing RLS policy violations on audit_partitioned - OpenAPI: remove permissioned_as/preserve_permissioned_as from EditSchedule - Add backwards-compat comments for schedule email writes Co-Authored-By: Claude Opus 4.6 (1M context) * chore: regenerate system prompts for permissioned_as field Co-Authored-By: Claude Opus 4.6 (1M context) * fix build * refactor: generalize onBehalfOf naming, add permissioned_as to EditSchedule - Frontend: rename onBehalfOfPermissionedAs -> onBehalfOf with comments explaining it carries emails for flows/scripts and permissioned_as for triggers/schedules - Frontend: rename getOnBehalfOfEmail -> getOnBehalfOf, getOnBehalfOfPermissionedAsForDeploy -> getOnBehalfOfForDeploy, customOnBehalfOfEmails -> customOnBehalfOf - Backend: add optional permissioned_as/preserve_permissioned_as to EditSchedule with COALESCE (only updates when provided) - Backend: add on_behalf_of audit log for schedule edit - Backend: remove unused resolve_on_behalf_of_permissioned_as - Tests: remove email assertions from schedule update test (email is just backwards compat, only permissioned_as matters) Co-Authored-By: Claude Opus 4.6 (1M context) * fix: preserve email column when permissioned_as is preserved on schedule edit Derive email from the preserved permissioned_as via cache lookup instead of always writing authed.email. This keeps the email column consistent with the old behavior for backwards compat with old workers. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: update deploy UI labels from "edited by" to "run as" for triggers Triggers now use permissioned_as (not edited_by) for permissions, so update the deploy UI wording to reflect this. Also update wm_deployers group description to mention schedules and permissioned_as. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: use u/username format for custom trigger/schedule deploy selection When picking a custom user for trigger/schedule deployment, store u/${username} (permissioned_as format) instead of the email. Flows/scripts continue to use email format for on_behalf_of_email. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: show u/username format for "me" option in trigger deploy selector Co-Authored-By: Claude Opus 4.6 (1M context) * refactor: simplify OnBehalfOfSelector to return the right format per kind OnBehalfOfSelector now handles the email vs permissioned_as format internally based on kind: - triggers: returns u/username, displays u/username in all options - flows/scripts/apps: returns email, displays username The onSelect callback now takes (choice, value?) where value is already in the correct format. Parent components just store it directly without needing to know about the format difference. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: always show u/username format in OnBehalfOfSelector for all kinds Display is now consistent: all kinds show u/username in the selector. The returned value still differs (email for flows/scripts, u/username for triggers) since the backend APIs expect different formats. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: replace email with permissioned_as in http_trigger test insert The email column was dropped from trigger tables in the migration. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: review fixes — migration, app policy, capture cleanup, naming - Migration: remove DEFAULT '', use nullable → populate → SET NOT NULL - App policy: set both on_behalf_of and on_behalf_of_email for all choices - OnBehalfOfSelector: return OnBehalfOfDetails {email, permissionedAs} instead of ambiguous value - Remove unused email field from Capture struct and query - Rename getSourceEmail/getTargetEmail → getSourceOnBehalfOf/getTargetOnBehalfOf - Rename test functions from preserve_email to preserve_permissioned_as Co-Authored-By: Claude Opus 4.6 (1M context) * fix: add permissioned_as to all test schedule INSERTs Since the migration no longer uses DEFAULT '', all INSERTs must explicitly provide permissioned_as. Updated test fixtures and schedule_push tests. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: strip permissioned_as from exports/sync, fix OpenAPI required field - Add permissioned_as to workspace export strip list (like edited_by) - Add permissioned_as to CLI TriggerFile Omit list - Fix TriggerExtraProperty.required: email → permissioned_as - Regenerate frontend and CLI types Co-Authored-By: Claude Opus 4.6 (1M context) * fix: remove accidentally committed generated files These directories are gitignored and should not be tracked. Co-Authored-By: Claude Opus 4.6 (1M context) * chore: regenerate system prompts for permissioned_as schema changes Co-Authored-By: Claude Opus 4.6 (1M context) * fix: remove permissioned_as from CLI TriggerFile Omit list Already stripped in workspace export, no need to also omit from the type. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: optimize email cache key and revert TriggerFile Omit change - Use single concatenated string for cache key instead of (String, String) tuple - Remove permissioned_as from CLI TriggerFile Omit (already stripped in export) Co-Authored-By: Claude Opus 4.6 (1M context) * fix: zero-allocation email cache lookups using Equivalent trait Use a borrowed EmailCacheKey(&str, &str) for cache lookups via quick_cache's Equivalent support. Only allocates (String, String) on cache miss for insert. This is called on every trigger fire and schedule push. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: add permissioned_as to Schedule required fields in OpenAPI spec The backend always returns permissioned_as (non-optional String), so the schema should reflect that. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: handle group- prefix in migration UPDATE statements edited_by can be 'group-{name}' for group-owned triggers/schedules. The migration now correctly maps these to 'g/{name}' format instead of incorrectly producing 'u/group-{name}'. Co-Authored-By: Claude Opus 4.6 (1M context) * Revert "fix: handle group- prefix in migration UPDATE statements" This reverts commit 0971392b38192ecd75096ec2c770d4df125f1836. * fix: use superadmin email to resolve permissioned_as in schedule migration For users upgrading from older versions where edited_by may not reflect the actual schedule owner, check if the email belongs to a superadmin and look up their username. Otherwise fall back to edited_by. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: fall back to superadmin email when not in workspace usr table If the superadmin isn't a member of the workspace, use their email as raw permissioned_as instead of falling back to edited_by. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: always update permissioned_as and email on schedule edit Consistent with pre-refactor behavior where email and edited_by were always updated on every edit. permissioned_as is now always set (to editing user or preserved value), removing the COALESCE that previously preserved it when not provided. Co-Authored-By: Claude Opus 4.6 (1M context) * feat: add schedule permission tests and centralize group prefix constants Tests: schedule create/update for normal user, workspace admin, and superadmin not in workspace. Verifies schedule fields (email, permissioned_as, edited_by) and pushed job fields (permissioned_as, permissioned_as_email). Constants: centralize "u/", "g/", "group-" as PERMISSIONED_AS_USER_PREFIX, PERMISSIONED_AS_GROUP_PREFIX, USERNAME_GROUP_PREFIX. Co-Authored-By: Claude Opus 4.6 (1M context) * fix: use @unknown.windmill.dev for synthetic email fallback Prevents privilege escalation: a user with username like 'superadmin_secret' would get superadmin via the synthetic email matching SUPERADMIN_SECRET_EMAIL. Using a different subdomain avoids any collision with hardcoded @windmill.dev emails. Co-Authored-By: Claude Opus 4.6 (1M context) * update ee ref * sqlx * chore: regenerate system prompts after main merge Co-Authored-By: Claude Opus 4.6 (1M context) * chore: update ee-repo-ref to bda51bc33bcb573659e7ff07d0a23ff6e23b8148 This commit updates the EE repository reference after PR #468 was merged in windmill-ee-private. Previous ee-repo-ref: 8cf1802f8fe183f430830590b4f3172a50207843 New ee-repo-ref: bda51bc33bcb573659e7ff07d0a23ff6e23b8148 Automated by sync-ee-ref workflow. --------- Co-authored-by: Claude Opus 4.6 (1M context) Co-authored-by: windmill-internal-app[bot] --- ...b747c84fcc12fbfd93c6c749570b94d35696.json} | 4 +- ...b00bc613ba7d29f8dd5ff31a4acd0ef3adfd.json} | 4 +- ...6529c37db0d5f18c83706bfbc63c8c3e4315.json} | 4 +- ...cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e.json | 35 + ...8a54ff53245271466b1f3a9d9edf43cdaa1f.json} | 4 +- ...891ea8449b4a9433908cf717738846f7eec5.json} | 4 +- ...e974a07881eb08164e885448d9a17c4ca6db.json} | 4 +- ...f81bf34e2d72f35ef1ea5d8c057ec9e694d8.json} | 4 +- ...8adf1ccc3a4923f988d8abaafc9749307c0e.json} | 4 +- ...0c4421fdae4aab496a3397c5c07d960582f2.json} | 4 +- ...06599401fcfa9765a58b8cd45a20b411d0ca.json} | 4 +- ...8ec1f990fce409160eb7b0e807bff56defff4.json | 34 + ...1a76d4e629ba027364190a2487cc9bd93235.json} | 6 +- ...14880c9bb43d0c3ce4ea9b4511aa2d092d0c.json} | 4 +- ...16da2e6743f3a45775cd556a7414bc8f9726.json} | 4 +- ...c91e98913397b310ff760c1dbb9077764e9b.json} | 4 +- ...d20e86dc0e6544f0bb22fa4ec68aa54a4dc8.json} | 47 +- ...37c7e56a5939d9ffcbee13e0264cbb2a6dfe.json} | 4 +- ...4ae91872a207ea2362e49e100ed1f0b35198.json} | 6 +- ...1600df8e55d50fa48d21ce2c5ae82eaf9f1c.json} | 4 +- ...ce24aa615fe1db7e8b4fc468963216aab69d.json} | 6 +- ...605e0f9d8c1f0fbe3271f0e65b1dc5cd47987.json | 18 + ...90077fe6c75f56a3419f530e542bab0fb1a4.json} | 6 +- ...97669676249dbf512ff39db94ecacc51e04e.json} | 4 +- ...b3bf95ed2e00ac07942d8bba2b11b8462d3b.json} | 5 +- ...4eb6729f1a658101bda2d0b0fca96b3cfee5.json} | 4 +- ...72029b2a4c90e06c05d4356b3ec6bd8d2748.json} | 4 +- ...358c964d9fb0f41bf2a1c834305b4d2398f2.json} | 4 +- ...ebc784e5da5ee25d47af187a75220d8fded7.json} | 4 +- ...c67d85089199c900afc6668f2336b803562e.json} | 4 +- ...4d848674d5318e20bb5fc6c4261da25744ca.json} | 4 +- ...46e5fbb157d1ec0684789a8d37eff1adf9a5.json} | 4 +- ...3b64f5f157fafe114d50fb910dc36ebe0f91.json} | 46 +- ...2bf3048c58bdf6fc67cb344eeb6cf72a9156.json} | 4 +- ...e55518fabafcadaf6458fdcf1095643534c5.json} | 4 +- ...89a5cc2af0439fe3c3c5829fd0776ec0734e.json} | 4 +- ...bce7cdbdd1feb553718cbde60bb8ccff4733.json} | 4 +- ...37ab3e02d8c3c10c53decc664533b8d04bc0.json} | 4 +- ...14babda8e6f9fe1ce71224773b2d5f307161.json} | 18 +- ...ff8535dfcb7c2b1143b7016fa17bc42a595d.json} | 4 +- ...c9cf1044ff173375a82c529babe053acb24f.json} | 4 +- ...c2cf970c3e41a223e31d3991276742d2afe1.json} | 4 +- ...81787a91ac587b1936c774de7a2a2094bf91.json} | 4 +- ...f0fef488102cd7b3fd38d8af3148545bf1a4.json} | 6 +- ...d1f80825a11cd61c0bb431678ad9ab4a138e.json} | 47 +- ...7df2663aca35b60e3c528f564da3114f0af1.json} | 4 +- ...542ec2d585ef672f0e1496944f4dd0d082f4.json} | 18 +- ...315b3a10db9aca72447b8b8afd9474baa0614.json | 18 + ...7deb97a89168fd7f74040e73033075140655.json} | 4 +- ...59fb2306f16c0c601668adaf30baf66f0fd0d.json | 17 + backend/Cargo.lock | 10 + backend/Cargo.toml | 9 + backend/ee-repo-ref.txt | 2 +- ...0260318000000_add_permissioned_as.down.sql | 30 + .../20260318000000_add_permissioned_as.up.sql | 64 ++ backend/tests/fixtures/permissions_test.sql | 8 +- .../tests/fixtures/preserve_on_behalf_of.sql | 8 + backend/tests/preserve_on_behalf_of.rs | 725 +++++++++++++++--- backend/tests/trigger_listener_queries.rs | 385 ++++++++++ backend/windmill-api-auth/src/auth.rs | 21 +- .../tests/fixtures/permissions_test.sql | 8 +- .../tests/trigger_e2e.rs | 28 +- .../tests/triggers.rs | 99 +-- backend/windmill-api-schedule/src/lib.rs | 139 ++-- backend/windmill-api/openapi.yaml | 164 ++-- .../windmill-api/src/triggers/http/handler.rs | 13 +- backend/windmill-api/src/workspaces_export.rs | 1 + backend/windmill-common/Cargo.toml | 1 + backend/windmill-common/src/auth.rs | 29 +- backend/windmill-common/src/users.rs | 79 +- .../windmill-common/tests/notify_events.rs | 4 +- backend/windmill-queue/src/jobs.rs | 17 +- backend/windmill-queue/src/schedule.rs | 27 +- backend/windmill-queue/tests/schedule_push.rs | 374 ++++----- backend/windmill-trigger-http/src/handler.rs | 18 +- .../src/http_trigger_auth.rs | 48 +- backend/windmill-trigger-http/src/lib.rs | 26 +- backend/windmill-trigger-mqtt/src/handler.rs | 16 +- .../windmill-trigger-postgres/src/handler.rs | 12 +- .../windmill-trigger-websocket/src/handler.rs | 16 +- .../windmill-trigger/src/global_handler.rs | 4 +- backend/windmill-trigger/src/handler.rs | 21 +- backend/windmill-trigger/src/listener.rs | 59 +- backend/windmill-trigger/src/types.rs | 45 +- backend/windmill-types/src/schedule.rs | 1 + cli/src/guidance/skills.ts | 36 + .../lib/components/CompareWorkspaces.svelte | 47 +- .../src/lib/components/DeployWorkspace.svelte | 43 +- .../src/lib/components/GroupEditor.svelte | 16 +- .../lib/components/OnBehalfOfSelector.svelte | 80 +- .../src/lib/components/ScriptBuilder.svelte | 88 ++- .../apps/editor/AppEditorHeaderDeploy.svelte | 24 +- .../flows/content/FlowSettings.svelte | 12 +- frontend/src/lib/utils_deployable.ts | 83 +- frontend/src/lib/utils_workspace_deploy.ts | 37 +- .../schemas/gcp_trigger.schema.yaml | 4 + .../schemas/http_trigger.schema.yaml | 4 + .../schemas/kafka_trigger.schema.yaml | 4 + .../schemas/mqtt_trigger.schema.yaml | 4 + .../schemas/nats_trigger.schema.yaml | 4 + .../schemas/postgres_trigger.schema.yaml | 4 + .../schemas/schedule.schema.yaml | 4 + .../schemas/sqs_trigger.schema.yaml | 4 + .../schemas/websocket_trigger.schema.yaml | 4 + 104 files changed, 2447 insertions(+), 1035 deletions(-) rename backend/.sqlx/{query-1d4bb4f53574ef95ef1016b760f849ec2372ac6a21bb2556d17a96dc72ea4980.json => query-02748cae17e8966dbd57a33017ccb747c84fcc12fbfd93c6c749570b94d35696.json} (68%) rename backend/.sqlx/{query-7c1ae9cac13d1387cfa94149f039054dd8c30c16b4657e73cdb0d7c7f1cb3b6d.json => query-02e04f9ebc0e14f98f290bf2dc3eb00bc613ba7d29f8dd5ff31a4acd0ef3adfd.json} (74%) rename backend/.sqlx/{query-1cad2ebfbdc46f9c0d93329897a71701f17a33b708b334d909563c9a0dcc9c23.json => query-066c9690d1606bf889879b7e3c686529c37db0d5f18c83706bfbc63c8c3e4315.json} (67%) create mode 100644 backend/.sqlx/query-0f26c74f604e1c3c613de8ba654cac1a41b20b1d3ea0f1a1c4ea2fcbbd314d7e.json rename backend/.sqlx/{query-6afa076744233fc5e92188ff978990fa3a704afe3eec523f4e203f7f6e247261.json => query-13d60d85694b5a5fcfc7687a07b78a54ff53245271466b1f3a9d9edf43cdaa1f.json} (71%) rename backend/.sqlx/{query-388ff2abd495cf71e87cf0c4ddc73b6c84867fb966df91b320c54acdd5e61315.json => query-1b7803a2060a19cb6e71f1e97619891ea8449b4a9433908cf717738846f7eec5.json} (69%) rename backend/.sqlx/{query-c723c3a5066a487b93e2642993f3bf624a1f50d06c7de75157420d97cf144763.json => query-1d2514b3d75ffb6cc0eb09ceb8fde974a07881eb08164e885448d9a17c4ca6db.json} (67%) rename backend/.sqlx/{query-1074c6c98e6a0c83ac04172a39abea21c793f58947051d39931d4da0868a1d77.json => query-1e28751bb98a1c477c0e582a2a39f81bf34e2d72f35ef1ea5d8c057ec9e694d8.json} (67%) rename backend/.sqlx/{query-45fc21026fa76e5d69f00a68a7be81abb3ec627578f2d14f0ce33896dc6ab4cf.json => query-1ef63255389bdc47d5392a84aad38adf1ccc3a4923f988d8abaafc9749307c0e.json} (66%) rename backend/.sqlx/{query-e486a64b76da5de97e404c81dd6e29d333ada2dcfbbddb028f37794b85778ca8.json => query-1f693e2fba9885f7fc49bd2994240c4421fdae4aab496a3397c5c07d960582f2.json} (63%) rename backend/.sqlx/{query-fb942aa7894b4ae904f0233405f62f201e3f5deed593128017b886342ef6d210.json => query-21d7ce033b5f67499f579aeae98806599401fcfa9765a58b8cd45a20b411d0ca.json} (70%) create mode 100644 backend/.sqlx/query-270cfaea4f888e73e21a957e0328ec1f990fce409160eb7b0e807bff56defff4.json rename backend/.sqlx/{query-8311a553c44221751ffdbbe6a997d6feba8d43292daf6c5433b66bd8450e8854.json => query-39062cdb183b97906c25602000321a76d4e629ba027364190a2487cc9bd93235.json} (62%) rename backend/.sqlx/{query-3d763dbb411e28ce026cc9ab525b20b409cfe17bf1cdef22aaffc719cf6c53e3.json => query-3aa3d0362fa8ed97dd034454b05f14880c9bb43d0c3ce4ea9b4511aa2d092d0c.json} (68%) rename backend/.sqlx/{query-ee9ee0fbf5dd72d190e18c56622244b984fdb36fbf70197d9f8cb6306c9670db.json => query-41ad9954bebe31b0545837147b1616da2e6743f3a45775cd556a7414bc8f9726.json} (81%) rename backend/.sqlx/{query-9f41ea5cbe4cffa74e4a283fe8f023c813e349956487f7b6599da452c068e9b9.json => query-4ba114f54ed88c27dd4aed05a273c91e98913397b310ff760c1dbb9077764e9b.json} (66%) rename backend/.sqlx/{query-987d79f7c6d7bc148cc8aab67e47161cfca045966e995e28c7a7ad090cffeda0.json => query-54b4c762add9b1ebfdb2a6d5abd6d20e86dc0e6544f0bb22fa4ec68aa54a4dc8.json} (79%) rename backend/.sqlx/{query-2fd0d3224382b000028d98b0af4c431d3cadd54cca65d83c1ab7f2d2972e2282.json => query-5efbf92ac7347e73769c66ffdc4037c7e56a5939d9ffcbee13e0264cbb2a6dfe.json} (77%) rename backend/.sqlx/{query-1cf2eb1426e8be89c3649272103bcd029e99b029b7f1b71eda4411d1e24e790d.json => query-6cfa6b5f16207863a03b77fffbb94ae91872a207ea2362e49e100ed1f0b35198.json} (78%) rename backend/.sqlx/{query-57b7236cae0b6a1940f4c2d4b202692450ee231488d9a55ca59ff53a6f674626.json => query-72bc0b4acb3fb155436df74b0bb11600df8e55d50fa48d21ce2c5ae82eaf9f1c.json} (69%) rename backend/.sqlx/{query-3cd37daa80bc3697d331c19e01a49916fc03fdf9eceff73fa153020b4a48f4a2.json => query-7402639802ba5f286db8436d21dcce24aa615fe1db7e8b4fc468963216aab69d.json} (81%) create mode 100644 backend/.sqlx/query-75b9a2153ba12ef443212d83743605e0f9d8c1f0fbe3271f0e65b1dc5cd47987.json rename backend/.sqlx/{query-17aafb72843659df9594d6d2466d2afaf26e666ffe52e0ea85792ea31b63410c.json => query-893ff34f2b22cf89a24a0b613ed390077fe6c75f56a3419f530e542bab0fb1a4.json} (63%) rename backend/.sqlx/{query-57eca702e951f5303a74643c7ba64472e2c2a781fbb6366d998a0f1ca22fcdf2.json => query-8d53b0f2df5fdb6c43b9f5e92c8a97669676249dbf512ff39db94ecacc51e04e.json} (74%) rename backend/.sqlx/{query-75ce9753a8acc8eccb3f2e0baaa5a871f866a8d21961fac8a003bc40c406ee79.json => query-906773b0de209734e9cf9c6421d4b3bf95ed2e00ac07942d8bba2b11b8462d3b.json} (65%) rename backend/.sqlx/{query-e99c958628d83e3fea054eaa182b1301e0b04c7eb9801d9de13b37bd908d902f.json => query-942c0abb55c910862fd45d3fa56a4eb6729f1a658101bda2d0b0fca96b3cfee5.json} (71%) rename backend/.sqlx/{query-97c60a4193a75b611db41543e7fe6da81fc631cc6ac43576f8a18afedad4d4a4.json => query-9883cff4f988767aeea7bce8b66672029b2a4c90e06c05d4356b3ec6bd8d2748.json} (67%) rename backend/.sqlx/{query-5dd6315ec270c268e905262e4b0a920837354d91a0ae16b1236c1267da71765f.json => query-a0a545fda5f3ebea0113d5daaf13358c964d9fb0f41bf2a1c834305b4d2398f2.json} (72%) rename backend/.sqlx/{query-072e5ab78f929c6b7264f98c1588cb24cc635836276ee6faa2438f494bfbce04.json => query-a37cfc632dd37cf37c06743239b5ebc784e5da5ee25d47af187a75220d8fded7.json} (65%) rename backend/.sqlx/{query-aa59a96bf2d7edfa7c550e66c4d52ddc8e84eacc633e361e49a5219d0bec94b9.json => query-a5b6ca174b7a3be1b6507ef40b9cc67d85089199c900afc6668f2336b803562e.json} (63%) rename backend/.sqlx/{query-0300afc35a880eef163dfdfd9d5299fac14562ee8595c792f3c30d042fa2d3eb.json => query-a8245a3b29927c26894be884c38e4d848674d5318e20bb5fc6c4261da25744ca.json} (55%) rename backend/.sqlx/{query-b5cda8eb32384f315689001f45676d4bf44cb4397dd0a722e7c9d035b58a09c1.json => query-aa7d3d159943250787a8ebc964cb46e5fbb157d1ec0684789a8d37eff1adf9a5.json} (63%) rename backend/.sqlx/{query-7927b80ce75d99b2a30f6b29196af000578a3c166509f032d14452cc637d884f.json => query-ad96768ff61fab1cfb9421683bb13b64f5f157fafe114d50fb910dc36ebe0f91.json} (82%) rename backend/.sqlx/{query-74c11f5a0315424574fe3e7429f967f7b94f9d9db7be628f9cb411d789085711.json => query-ae8d7c07a4027bccf404951e75992bf3048c58bdf6fc67cb344eeb6cf72a9156.json} (73%) rename backend/.sqlx/{query-e25aa749cfc9f0bb1649d162e36f2c0ce2187e47d745aa5ba96f9453c722750c.json => query-c301e668a5b88741e6c20700909be55518fabafcadaf6458fdcf1095643534c5.json} (73%) rename backend/.sqlx/{query-888f7e82c25b677172a276d042ae7066113f2a522068f04c8cee0895a49b787f.json => query-c5b650748a4ac0d0a04e287d1b3c89a5cc2af0439fe3c3c5829fd0776ec0734e.json} (78%) rename backend/.sqlx/{query-6c3c38ed5a0e6de0c97954fe4581daa788a7551b4a00dd33f78d36e246b98dd7.json => query-c7aed7fe3b6774477d403bc3e7fcbce7cdbdd1feb553718cbde60bb8ccff4733.json} (62%) rename backend/.sqlx/{query-8c3cc09c1bbb6209467c75723dd02e97dddb99789422012e85e86c3151a9f2e9.json => query-ccd76be88fa9c11b3dc2e6d7711437ab3e02d8c3c10c53decc664533b8d04bc0.json} (67%) rename backend/.sqlx/{query-de656102e898ebc90e53af8d36c882cf47e798567bb733e8b528dcedc48ad4bf.json => query-cd410aa458ad23d47945084a0b9614babda8e6f9fe1ce71224773b2d5f307161.json} (68%) rename backend/.sqlx/{query-f4340e31adddc9a37bab64957b2b1b9b29f978c73f003dfbf81d5c487f666358.json => query-d082ff0fcc2bb02257ab82376d87ff8535dfcb7c2b1143b7016fa17bc42a595d.json} (62%) rename backend/.sqlx/{query-8642e4fc3efb011a8b7f98d2080a165ec4046c3a7172b5635b818c0fa133411f.json => query-d35300ab94202c181ee5d1e1c76ec9cf1044ff173375a82c529babe053acb24f.json} (66%) rename backend/.sqlx/{query-1bf8dc01326ebf6b8faa04e418b781e37bb9cedd1a89bf71a969b6db8cace48e.json => query-d56a8a7291ce3141c06d79ef854ac2cf970c3e41a223e31d3991276742d2afe1.json} (73%) rename backend/.sqlx/{query-c659cfe81441bb1b62a9f23f5d9b7f5b1f354b6bb0a78d007114fed084cfe292.json => query-db9957ea9b5b326c27ff62f96a3981787a91ac587b1936c774de7a2a2094bf91.json} (68%) rename backend/.sqlx/{query-075d4749299af2cb81162bf396bec6aa89de43ec201c911196763e03e644ca7a.json => query-dbb16284b9dd98b9339816e43eebf0fef488102cd7b3fd38d8af3148545bf1a4.json} (63%) rename backend/.sqlx/{query-23e4c6e3dc6a48f702c2b26a6b1f94668e086caaa0093a3b685f87483513b0d2.json => query-dd20f94d560238096390371c98ded1f80825a11cd61c0bb431678ad9ab4a138e.json} (69%) rename backend/.sqlx/{query-cad914554762b6be72b289814f77072600f8f1bc1fa73ef6f63972775aedf044.json => query-de4879b8e3002ca48005406c4e107df2663aca35b60e3c528f564da3114f0af1.json} (73%) rename backend/.sqlx/{query-3aad6340ea1f8dc8411742ef5de5a77d5de903845da9e686aef804bc09db1687.json => query-e3f09fe777cbbc009ca1af31989b542ec2d585ef672f0e1496944f4dd0d082f4.json} (67%) create mode 100644 backend/.sqlx/query-e998bb8195b4fafe6de20fb0bf4315b3a10db9aca72447b8b8afd9474baa0614.json rename backend/.sqlx/{query-e485c82978d10379c6d1b7cd850f3bf764e6c5bf775f3d710ba36a79f5e424eb.json => query-f57a891d1b507ec6767099a388be7deb97a89168fd7f74040e73033075140655.json} (68%) create mode 100644 backend/.sqlx/query-f5f18e6c5b7376b2760915ee3c859fb2306f16c0c601668adaf30baf66f0fd0d.json create mode 100644 backend/migrations/20260318000000_add_permissioned_as.down.sql create mode 100644 backend/migrations/20260318000000_add_permissioned_as.up.sql create mode 100644 backend/tests/trigger_listener_queries.rs 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}