fix: strip server-owned credential status and correct expiry copy

This commit is contained in:
hugocasa
2026-09-02 12:17:29 +02:00
parent 41808982da
commit 9750bbb3d8
8 changed files with 95 additions and 30 deletions
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value FROM resource WHERE workspace_id = $1 AND path = $2 FOR UPDATE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Jsonb"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
true
]
},
"hash": "0222c6722a85ba1a86a1535c4c8fde581934864199fa0a484c9a8b859f86ec7d"
}
@@ -1,16 +0,0 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_sync = jsonb_set(git_sync, '{repositories}',\n (SELECT jsonb_agg(\n CASE WHEN elem->>'git_repo_resource_path' = $2\n THEN CASE WHEN $3::jsonb = 'null'::jsonb\n THEN elem - 'credential'\n ELSE jsonb_set(elem, '{credential}', $3) END\n ELSE elem END)\n FROM jsonb_array_elements(git_sync->'repositories') AS elem)\n )\n WHERE workspace_id = $1\n AND jsonb_typeof(git_sync->'repositories') = 'array'\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Jsonb"
]
},
"nullable": []
},
"hash": "45e23417d12e4623016f89efd5d0f24f52437daa796ee99eec77c2cef135c9f6"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n UPDATE workspace_settings\n SET git_sync = jsonb_set(git_sync, '{repositories}',\n COALESCE((SELECT jsonb_agg(\n CASE WHEN elem->>'git_repo_resource_path' = $2\n THEN CASE WHEN $3::jsonb = 'null'::jsonb\n THEN elem - 'credential'\n ELSE jsonb_set(elem, '{credential}', $3) END\n ELSE elem END)\n FROM jsonb_array_elements(git_sync->'repositories') AS elem), '[]'::jsonb)\n )\n WHERE workspace_id = $1\n AND jsonb_typeof(git_sync->'repositories') = 'array'\n AND EXISTS (\n SELECT 1 FROM jsonb_array_elements(git_sync->'repositories') AS e\n WHERE e->>'git_repo_resource_path' = $2\n )\n ",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Text",
"Jsonb"
]
},
"nullable": []
},
"hash": "81e7f902c40d72937b494cd9f3f8c9fe4348d179add37361353fc9a02d96f2fa"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "SELECT value FROM variable WHERE path = $1 AND workspace_id = $2 FOR UPDATE",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "value",
"type_info": "Varchar"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false
]
},
"hash": "8d9f10a4fed1f8422224d26a4e3b1eed51f6ad271f4c76d5b430690e0ae73c1b"
}
+1 -1
View File
@@ -1 +1 @@
b342a1bdeda7ebba2da64c71ced6ef5dfac48663
7d11df2f77c8af2da18269532fe276b307dd2a54
+12 -9
View File
@@ -353,9 +353,7 @@ pub async fn initial_load(
)
}
});
pass.action(windmill_common::min_version::store_min_keep_alive_version(
db,
));
pass.action(windmill_common::min_version::store_min_keep_alive_version(db));
pass.setting(
windmill_common::global_settings::INSTANCE_EVENTS_WEBHOOK_SETTING,
false,
@@ -701,6 +699,7 @@ pub async fn initial_load(
pass.run(conn).await;
}
pub fn apply_metrics_enabled(value: Option<serde_json::Value>) {
if let Some(serde_json::Value::Bool(t)) = value {
METRICS_ENABLED.store(t, Ordering::Relaxed)
@@ -1057,8 +1056,8 @@ pub fn apply_fork_workspace_tag_append_fork_suffix(value: Option<serde_json::Val
}
pub async fn reload_critical_alert_mute_ui_setting(conn: &Connection) -> error::Result<()> {
let v = load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true)
.await?;
let v =
load_value_from_global_settings_with_conn(conn, CRITICAL_ALERT_MUTE_UI_SETTING, true).await?;
apply_critical_alert_mute_ui_setting(v);
Ok(())
}
@@ -2657,6 +2656,7 @@ pub async fn reload_timeout_wait_result_setting(conn: &Connection) {
.await;
}
pub async fn reload_extra_pip_index_url_setting(conn: &Connection) {
reload_option_setting_with_tracing(
conn,
@@ -2747,6 +2747,7 @@ pub async fn reload_bunfig_install_scopes_setting(conn: &Connection) {
.await;
}
pub async fn reload_nuget_config_setting(conn: &Connection) {
reload_option_setting_with_tracing(
conn,
@@ -2854,6 +2855,7 @@ pub async fn reload_ruby_repos_setting(conn: &Connection) {
.await;
}
pub async fn reload_workspace_registries_setting(conn: &Connection) {
match load_value_from_global_settings_with_conn(
conn,
@@ -3092,6 +3094,7 @@ pub async fn apply_job_isolation_setting(value: Option<serde_json::Value>) {
}
}
async fn resolve_license_key_value(conn: &Connection, quiet: bool) -> anyhow::Result<String> {
let q = load_value_from_global_settings_with_conn(conn, LICENSE_KEY_SETTING, true)
.await
@@ -3386,10 +3389,7 @@ impl<'a> SettingsPass<'a> {
// on compile-time defaults until the next full reload. Only the single-query transport
// can fail this way; over HTTP the batch already is the per-setting read.
if matches!(conn, Connection::Sql(_)) && values.is_empty() && !names.is_empty() {
tracing::warn!(
"Falling back to per-setting reads for {} settings",
names.len()
);
tracing::warn!("Falling back to per-setting reads for {} settings", names.len());
values = fetch_settings_individually(conn, &names).await;
}
for (name, http) in &declared {
@@ -3781,6 +3781,7 @@ pub fn parse_setting_value<T: FromStr + DeserializeOwned + Display>(
value
}
#[cfg(feature = "prometheus")]
pub async fn monitor_pool(db: &DB) {
if METRICS_ENABLED.load(Ordering::Relaxed) {
@@ -4717,6 +4718,7 @@ async fn maintain_git_credentials_inner(db: &Pool<Postgres>) -> error::Result<()
Ok(())
}
#[cfg(feature = "private")]
#[cfg(feature = "private")]
async fn poll_git_auto_pull_inner(db: &Pool<Postgres>) -> error::Result<()> {
use windmill_common::workspaces::{AutoPullMode, WorkspaceGitSyncSettings};
@@ -6614,6 +6616,7 @@ pub async fn reload_critical_alerts_on_db_oversize(conn: &DB) -> error::Result<(
Ok(())
}
pub async fn reload_jwt_secret_setting(db: &DB) -> error::Result<()> {
let v = load_value_from_global_settings(db, JWT_SECRET_SETTING).await?;
apply_jwt_secret_setting(db, v).await
@@ -3937,6 +3937,7 @@ async fn edit_git_sync_config(
clear_client_supplied_auto_pull_state(ap);
}
repo.open_pr_error = None;
repo.credential = None;
}
reject_parent_only_git_sync_settings_on_fork(
&db,
@@ -4037,6 +4038,7 @@ async fn edit_git_sync_config(
continue;
};
repo.open_pr_error = old.open_pr_error.clone();
repo.credential = old.credential.clone();
if let (Some(new_ap), Some(old_ap)) =
(repo.auto_pull.as_mut(), old.auto_pull.as_ref())
{
@@ -4180,6 +4182,7 @@ async fn edit_git_sync_repository(
clear_client_supplied_auto_pull_state(ap);
}
new_config.repository.open_pr_error = None;
new_config.repository.credential = None;
reject_parent_only_git_sync_settings_on_fork(
&db,
&w_id,
@@ -4304,6 +4307,7 @@ async fn edit_git_sync_repository(
// from the UI cannot revert what the poller/webhook layer wrote.
let mut updated = new_config.repository;
updated.open_pr_error = existing_repo.open_pr_error.clone();
updated.credential = existing_repo.credential.clone();
match (updated.auto_pull.as_mut(), existing_repo.auto_pull.as_ref()) {
(Some(new_ap), Some(old_ap)) => {
new_ap.last_synced_sha = old_ap.last_synced_sha.clone();
@@ -6181,6 +6185,7 @@ async fn update_workspace_settings(
r.auto_pull = None;
r.fork_open_prs = false;
r.open_pr_error = None;
r.credential = None;
r
})
.collect();
@@ -8172,6 +8177,7 @@ async fn attach_dev_workspace(
r.auto_pull = None;
r.fork_open_prs = false;
r.open_pr_error = None;
r.credential = None;
}
let serialized =
serde_json::to_value(&settings).map_err(|e| Error::internal_err(e.to_string()))?;
@@ -155,13 +155,20 @@
const MS_PER_DAY = 86_400_000
/**
* Days until the repository's own token expires, or undefined when it never
* expires and when nothing has checked it yet.
* Whole days until the repository's own token expires, or undefined when it
* never expires and when nothing has checked it yet.
*
* Counted between calendar dates, not instants: GitLab expires a token on a
* date, so measuring from "now" would call a token expiring later today
* expired, and one expiring tomorrow today's problem.
*/
const credentialDaysLeft = $derived.by(() => {
const expiresAt = repo.credential?.expires_at
if (!expiresAt) return undefined
return Math.floor((new Date(`${expiresAt}T00:00:00Z`).getTime() - Date.now()) / MS_PER_DAY)
const expiry = new Date(`${expiresAt}T00:00:00Z`).getTime()
const now = new Date()
const today = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
return Math.round((expiry - today) / MS_PER_DAY)
})
/**
@@ -583,9 +590,12 @@
<div class="text-xs text-secondary">
{#if credentialDaysLeft === undefined}
Repository token does not expire.
{:else}
{:else if repo.credential.rotatable}
Repository token expires on {repo.credential.expires_at}, and Windmill renews it
automatically.
{:else}
Repository token expires on {repo.credential.expires_at}. Give it the api or self_rotate
scope to let Windmill renew it automatically.
{/if}
</div>
{/if}