From e2103d944876fd4ec59ded69c2f25782c8a8f534 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Wed, 4 Mar 2026 09:12:44 +0000 Subject: [PATCH] refactor: keep writing plaintext token column for backward compat Write to token column alongside token_hash until MIN_VERSION_SUPPORTS_TOKEN_HASH (1.649.0) is reached. This ensures older workers can still authenticate during rolling upgrades. Remove the separate UPDATE in new_webhook_token since create_token_internal now writes plaintext directly. Co-Authored-By: Claude Opus 4.6 --- ...2e1f4901b0382fca50704c9544cdec5f9352c.json | 21 ++++++++++++++++++ ...25915f671e7c52426fc54b2fd533b90596e2.json} | 5 +++-- ...7d10306436222bc5b6cb0403507bd465254d8.json | 21 ------------------ ...104e3a9bf44ff175993bda6eb1848d155b39c.json | 20 ----------------- ...dc10bd94fae3955d2a27d8b2a7b4ae15e01b3.json | 15 ------------- ...c57375fdb528a30de49adbe5305fc237b2c5.json} | 5 +++-- ...675a2a05eaf30237e21359c52f31bb1bddc73.json | 22 +++++++++++++++++++ ...fdf104e3d77b0725c5355305d19f67359e90.json} | 5 +++-- backend/windmill-api-auth/src/lib.rs | 9 ++++---- backend/windmill-api-users/src/users.rs | 10 +++++---- backend/windmill-api/src/mcp/oauth_server.rs | 14 +++++++----- backend/windmill-common/src/min_version.rs | 1 + .../windmill-native-triggers/src/handler.rs | 10 --------- 13 files changed, 72 insertions(+), 86 deletions(-) create mode 100644 backend/.sqlx/query-223fbd972728d5b3ec5b1708e3f2e1f4901b0382fca50704c9544cdec5f9352c.json rename backend/.sqlx/{query-b7ed94e891ad12a03a6d9083772aa7c9bfcecdd1863b64f9d75bb73805bc72c8.json => query-54c0c20fe025d4fb45f04ff3389b25915f671e7c52426fc54b2fd533b90596e2.json} (57%) delete mode 100644 backend/.sqlx/query-5926c452cf9af4e915f45e526667d10306436222bc5b6cb0403507bd465254d8.json delete mode 100644 backend/.sqlx/query-5efdf09fc38aed0dea162979444104e3a9bf44ff175993bda6eb1848d155b39c.json delete mode 100644 backend/.sqlx/query-731b2ed1c0aa0c579ad174e3417dc10bd94fae3955d2a27d8b2a7b4ae15e01b3.json rename backend/.sqlx/{query-be51860f18da9c516cd926efa8d5b35bcfbd01015274c38deb21ed7edb718983.json => query-98d929e1c12a41933f5cfbd79175c57375fdb528a30de49adbe5305fc237b2c5.json} (61%) create mode 100644 backend/.sqlx/query-9f86d16016ddbed5ff2a87c113a675a2a05eaf30237e21359c52f31bb1bddc73.json rename backend/.sqlx/{query-350c533e0e829bc9fb1dd8d5da0935839c19581c6eda8486f83be4cf859a32aa.json => query-d05f20431cd08f737bfbf904efedfdf104e3d77b0725c5355305d19f67359e90.json} (59%) diff --git a/backend/.sqlx/query-223fbd972728d5b3ec5b1708e3f2e1f4901b0382fca50704c9544cdec5f9352c.json b/backend/.sqlx/query-223fbd972728d5b3ec5b1708e3f2e1f4901b0382fca50704c9544cdec5f9352c.json new file mode 100644 index 0000000000..8595b12ec6 --- /dev/null +++ b/backend/.sqlx/query-223fbd972728d5b3ec5b1708e3f2e1f4901b0382fca50704c9544cdec5f9352c.json @@ -0,0 +1,21 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO token (token_hash, token_prefix, token, email, label, expiration, scopes, workspace_id)\n SELECT $1::varchar, $2::varchar, $3::varchar, $4::varchar, $5::varchar, now() + ($6 || ' seconds')::interval, $7::text[], $8::varchar\n WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $8 AND deleted = true)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Text", + "TextArray", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "223fbd972728d5b3ec5b1708e3f2e1f4901b0382fca50704c9544cdec5f9352c" +} diff --git a/backend/.sqlx/query-b7ed94e891ad12a03a6d9083772aa7c9bfcecdd1863b64f9d75bb73805bc72c8.json b/backend/.sqlx/query-54c0c20fe025d4fb45f04ff3389b25915f671e7c52426fc54b2fd533b90596e2.json similarity index 57% rename from backend/.sqlx/query-b7ed94e891ad12a03a6d9083772aa7c9bfcecdd1863b64f9d75bb73805bc72c8.json rename to backend/.sqlx/query-54c0c20fe025d4fb45f04ff3389b25915f671e7c52426fc54b2fd533b90596e2.json index c8a2f6dda1..de1b71a1e6 100644 --- a/backend/.sqlx/query-b7ed94e891ad12a03a6d9083772aa7c9bfcecdd1863b64f9d75bb73805bc72c8.json +++ b/backend/.sqlx/query-54c0c20fe025d4fb45f04ff3389b25915f671e7c52426fc54b2fd533b90596e2.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO token\n (token_hash, token_prefix, email, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)", + "query": "INSERT INTO token\n (token_hash, token_prefix, token, email, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, $5, now() + ($6 || ' seconds')::interval, $7)", "describe": { "columns": [], "parameters": { @@ -9,11 +9,12 @@ "Varchar", "Varchar", "Varchar", + "Varchar", "Text", "Bool" ] }, "nullable": [] }, - "hash": "b7ed94e891ad12a03a6d9083772aa7c9bfcecdd1863b64f9d75bb73805bc72c8" + "hash": "54c0c20fe025d4fb45f04ff3389b25915f671e7c52426fc54b2fd533b90596e2" } diff --git a/backend/.sqlx/query-5926c452cf9af4e915f45e526667d10306436222bc5b6cb0403507bd465254d8.json b/backend/.sqlx/query-5926c452cf9af4e915f45e526667d10306436222bc5b6cb0403507bd465254d8.json deleted file mode 100644 index 239267f8ec..0000000000 --- a/backend/.sqlx/query-5926c452cf9af4e915f45e526667d10306436222bc5b6cb0403507bd465254d8.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO token\n (token_hash, token_prefix, email, label, expiration, super_admin, scopes, workspace_id)\n SELECT $1, $2, $3, $4, $5, $6, $7, $8\n WHERE $8::varchar IS NULL OR NOT EXISTS(\n SELECT 1 FROM workspace WHERE id = $8 AND deleted = true\n )", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Timestamptz", - "Bool", - "TextArray", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "5926c452cf9af4e915f45e526667d10306436222bc5b6cb0403507bd465254d8" -} diff --git a/backend/.sqlx/query-5efdf09fc38aed0dea162979444104e3a9bf44ff175993bda6eb1848d155b39c.json b/backend/.sqlx/query-5efdf09fc38aed0dea162979444104e3a9bf44ff175993bda6eb1848d155b39c.json deleted file mode 100644 index a371cd8626..0000000000 --- a/backend/.sqlx/query-5efdf09fc38aed0dea162979444104e3a9bf44ff175993bda6eb1848d155b39c.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO token (token_hash, token_prefix, email, label, expiration, scopes, workspace_id)\n SELECT $1::varchar, $2::varchar, $3::varchar, $4::varchar, now() + ($5 || ' seconds')::interval, $6::text[], $7::varchar\n WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $7 AND deleted = true)", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar", - "Varchar", - "Text", - "TextArray", - "Varchar" - ] - }, - "nullable": [] - }, - "hash": "5efdf09fc38aed0dea162979444104e3a9bf44ff175993bda6eb1848d155b39c" -} diff --git a/backend/.sqlx/query-731b2ed1c0aa0c579ad174e3417dc10bd94fae3955d2a27d8b2a7b4ae15e01b3.json b/backend/.sqlx/query-731b2ed1c0aa0c579ad174e3417dc10bd94fae3955d2a27d8b2a7b4ae15e01b3.json deleted file mode 100644 index 1bacc43222..0000000000 --- a/backend/.sqlx/query-731b2ed1c0aa0c579ad174e3417dc10bd94fae3955d2a27d8b2a7b4ae15e01b3.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "UPDATE token SET token = $1 WHERE token_hash = $2", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Text" - ] - }, - "nullable": [] - }, - "hash": "731b2ed1c0aa0c579ad174e3417dc10bd94fae3955d2a27d8b2a7b4ae15e01b3" -} diff --git a/backend/.sqlx/query-be51860f18da9c516cd926efa8d5b35bcfbd01015274c38deb21ed7edb718983.json b/backend/.sqlx/query-98d929e1c12a41933f5cfbd79175c57375fdb528a30de49adbe5305fc237b2c5.json similarity index 61% rename from backend/.sqlx/query-be51860f18da9c516cd926efa8d5b35bcfbd01015274c38deb21ed7edb718983.json rename to backend/.sqlx/query-98d929e1c12a41933f5cfbd79175c57375fdb528a30de49adbe5305fc237b2c5.json index 722b24e433..69cd713502 100644 --- a/backend/.sqlx/query-be51860f18da9c516cd926efa8d5b35bcfbd01015274c38deb21ed7edb718983.json +++ b/backend/.sqlx/query-98d929e1c12a41933f5cfbd79175c57375fdb528a30de49adbe5305fc237b2c5.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO token\n (token_hash, token_prefix, email, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, $5, $6)", + "query": "INSERT INTO token\n (token_hash, token_prefix, token, email, label, expiration, super_admin)\n VALUES ($1, $2, $3, $4, $5, $6, $7)", "describe": { "columns": [], "parameters": { @@ -9,11 +9,12 @@ "Varchar", "Varchar", "Varchar", + "Varchar", "Timestamptz", "Bool" ] }, "nullable": [] }, - "hash": "be51860f18da9c516cd926efa8d5b35bcfbd01015274c38deb21ed7edb718983" + "hash": "98d929e1c12a41933f5cfbd79175c57375fdb528a30de49adbe5305fc237b2c5" } diff --git a/backend/.sqlx/query-9f86d16016ddbed5ff2a87c113a675a2a05eaf30237e21359c52f31bb1bddc73.json b/backend/.sqlx/query-9f86d16016ddbed5ff2a87c113a675a2a05eaf30237e21359c52f31bb1bddc73.json new file mode 100644 index 0000000000..163dc2285b --- /dev/null +++ b/backend/.sqlx/query-9f86d16016ddbed5ff2a87c113a675a2a05eaf30237e21359c52f31bb1bddc73.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO token\n (token_hash, token_prefix, token, email, label, expiration, super_admin, scopes, workspace_id)\n SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9\n WHERE $9::varchar IS NULL OR NOT EXISTS(\n SELECT 1 FROM workspace WHERE id = $9 AND deleted = true\n )", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Varchar", + "Timestamptz", + "Bool", + "TextArray", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "9f86d16016ddbed5ff2a87c113a675a2a05eaf30237e21359c52f31bb1bddc73" +} diff --git a/backend/.sqlx/query-350c533e0e829bc9fb1dd8d5da0935839c19581c6eda8486f83be4cf859a32aa.json b/backend/.sqlx/query-d05f20431cd08f737bfbf904efedfdf104e3d77b0725c5355305d19f67359e90.json similarity index 59% rename from backend/.sqlx/query-350c533e0e829bc9fb1dd8d5da0935839c19581c6eda8486f83be4cf859a32aa.json rename to backend/.sqlx/query-d05f20431cd08f737bfbf904efedfdf104e3d77b0725c5355305d19f67359e90.json index 46d0a2c2cf..e778c17bf6 100644 --- a/backend/.sqlx/query-350c533e0e829bc9fb1dd8d5da0935839c19581c6eda8486f83be4cf859a32aa.json +++ b/backend/.sqlx/query-d05f20431cd08f737bfbf904efedfdf104e3d77b0725c5355305d19f67359e90.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO token\n (token_hash, token_prefix, label, super_admin, email)\n VALUES ($1, $2, $3, $4, $5)", + "query": "INSERT INTO token\n (token_hash, token_prefix, token, label, super_admin, email)\n VALUES ($1, $2, $3, $4, $5, $6)", "describe": { "columns": [], "parameters": { @@ -8,11 +8,12 @@ "Varchar", "Varchar", "Varchar", + "Varchar", "Bool", "Varchar" ] }, "nullable": [] }, - "hash": "350c533e0e829bc9fb1dd8d5da0935839c19581c6eda8486f83be4cf859a32aa" + "hash": "d05f20431cd08f737bfbf904efedfdf104e3d77b0725c5355305d19f67359e90" } diff --git a/backend/windmill-api-auth/src/lib.rs b/backend/windmill-api-auth/src/lib.rs index a81e69440b..77e1dd737b 100644 --- a/backend/windmill-api-auth/src/lib.rs +++ b/backend/windmill-api-auth/src/lib.rs @@ -541,13 +541,14 @@ pub async fn create_token_internal( } let rows = sqlx::query!( "INSERT INTO token - (token_hash, token_prefix, email, label, expiration, super_admin, scopes, workspace_id) - SELECT $1, $2, $3, $4, $5, $6, $7, $8 - WHERE $8::varchar IS NULL OR NOT EXISTS( - SELECT 1 FROM workspace WHERE id = $8 AND deleted = true + (token_hash, token_prefix, token, email, label, expiration, super_admin, scopes, workspace_id) + SELECT $1, $2, $3, $4, $5, $6, $7, $8, $9 + WHERE $9::varchar IS NULL OR NOT EXISTS( + SELECT 1 FROM workspace WHERE id = $9 AND deleted = true )", t_hash, t_prefix, + &token, authed.email, token_config.label, token_config.expiration, diff --git a/backend/windmill-api-users/src/users.rs b/backend/windmill-api-users/src/users.rs index f024181538..94f1d92954 100644 --- a/backend/windmill-api-users/src/users.rs +++ b/backend/windmill-api-users/src/users.rs @@ -1778,10 +1778,11 @@ pub async fn create_session_token<'c>( sqlx::query!( "INSERT INTO token - (token_hash, token_prefix, email, label, expiration, super_admin) - VALUES ($1, $2, $3, $4, now() + ($5 || ' seconds')::interval, $6)", + (token_hash, token_prefix, token, email, label, expiration, super_admin) + VALUES ($1, $2, $3, $4, $5, now() + ($6 || ' seconds')::interval, $7)", t_hash, t_prefix, + &token, email, "session", &MAX_SESSION_VALIDITY_SECONDS.to_string(), @@ -1850,10 +1851,11 @@ async fn impersonate( sqlx::query!( "INSERT INTO token - (token_hash, token_prefix, email, label, expiration, super_admin) - VALUES ($1, $2, $3, $4, $5, $6)", + (token_hash, token_prefix, token, email, label, expiration, super_admin) + VALUES ($1, $2, $3, $4, $5, $6, $7)", t_hash, t_prefix, + &token, impersonated, new_token.label, new_token.expiration, diff --git a/backend/windmill-api/src/mcp/oauth_server.rs b/backend/windmill-api/src/mcp/oauth_server.rs index c18a649f61..d5452fd723 100644 --- a/backend/windmill-api/src/mcp/oauth_server.rs +++ b/backend/windmill-api/src/mcp/oauth_server.rs @@ -392,11 +392,12 @@ async fn handle_authorization_code_grant( // Create access token (rejects archived workspaces inline) let rows = sqlx::query!( - "INSERT INTO token (token_hash, token_prefix, email, label, expiration, scopes, workspace_id) - SELECT $1::varchar, $2::varchar, $3::varchar, $4::varchar, now() + ($5 || ' seconds')::interval, $6::text[], $7::varchar - WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $7 AND deleted = true)", + "INSERT INTO token (token_hash, token_prefix, token, email, label, expiration, scopes, workspace_id) + SELECT $1::varchar, $2::varchar, $3::varchar, $4::varchar, $5::varchar, now() + ($6 || ' seconds')::interval, $7::text[], $8::varchar + WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $8 AND deleted = true)", access_token_hash, access_token_prefix, + &access_token, auth_code.user_email, format!("mcp-oauth-{}", auth_code.client_id), MCP_OAUTH_TOKEN_EXPIRATION_SECS.to_string(), @@ -529,11 +530,12 @@ async fn handle_refresh_token_grant( // Create new access token (rejects archived workspaces inline) let rows = sqlx::query!( - "INSERT INTO token (token_hash, token_prefix, email, label, expiration, scopes, workspace_id) - SELECT $1::varchar, $2::varchar, $3::varchar, $4::varchar, now() + ($5 || ' seconds')::interval, $6::text[], $7::varchar - WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $7 AND deleted = true)", + "INSERT INTO token (token_hash, token_prefix, token, email, label, expiration, scopes, workspace_id) + SELECT $1::varchar, $2::varchar, $3::varchar, $4::varchar, $5::varchar, now() + ($6 || ' seconds')::interval, $7::text[], $8::varchar + WHERE NOT EXISTS(SELECT 1 FROM workspace WHERE id = $8 AND deleted = true)", new_access_token_hash, new_access_token_prefix, + &new_access_token, token_row.user_email, format!("mcp-oauth-{}", token_row.client_id), MCP_OAUTH_TOKEN_EXPIRATION_SECS.to_string(), diff --git a/backend/windmill-common/src/min_version.rs b/backend/windmill-common/src/min_version.rs index fd1769dd55..61a6e74fae 100644 --- a/backend/windmill-common/src/min_version.rs +++ b/backend/windmill-common/src/min_version.rs @@ -5,6 +5,7 @@ use tokio::sync::RwLock; // ============ Feature Definitions ============ +pub const MIN_VERSION_SUPPORTS_TOKEN_HASH: VC = vc(1, 649, 0, "Token hash storage"); pub const MIN_VERSION_SUPPORTS_SYNC_JOBS_DEBOUNCING: VC = vc(1, 602, 0, "Sync jobs debouncing"); pub const MIN_VERSION_SUPPORTS_DEBOUNCING_V2: VC = vc(1, 597, 0, "Debouncing V2"); pub const MIN_VERSION_IS_AT_LEAST_1_595: VC = vc(1, 595, 0, "Flow status separate table"); diff --git a/backend/windmill-native-triggers/src/handler.rs b/backend/windmill-native-triggers/src/handler.rs index ee122f31ce..9384bde8b6 100644 --- a/backend/windmill-native-triggers/src/handler.rs +++ b/backend/windmill-native-triggers/src/handler.rs @@ -94,16 +94,6 @@ async fn new_webhook_token( ); let token = create_token_internal(&mut *tx, &db, &authed, token_config).await?; - // Store plaintext for webhook tokens — needed for URL reconstruction on channel renewal - let t_hash = windmill_common::auth::hash_token(&token); - sqlx::query!( - "UPDATE token SET token = $1 WHERE token_hash = $2", - &token, - &t_hash - ) - .execute(&mut *tx) - .await?; - Ok(token) }