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 <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-03-04 09:12:44 +00:00
co-authored by Claude Opus 4.6
parent 790a18c209
commit e2103d9448
13 changed files with 72 additions and 86 deletions
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
@@ -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"
}
+5 -4
View File
@@ -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,
+6 -4
View File
@@ -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,
+8 -6
View File
@@ -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(),
@@ -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");
@@ -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)
}