Custom instance db password safety (#7313)

* update parsers version

* safety custom instance db migration

* Refresh custom instance pwd button

* nit indent

* nit button accent

* Prevent leave without saving
This commit is contained in:
Diego Imbert
2025-12-08 17:35:39 +01:00
committed by GitHub
parent eb284dfabd
commit df884e6135
10 changed files with 139 additions and 21 deletions

View File

@@ -0,0 +1 @@
-- Add down migration script here

View File

@@ -0,0 +1,32 @@
DO $$
DECLARE
pwd text;
BEGIN
SELECT gen_random_uuid()::text INTO pwd;
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'custom_instance_user') THEN
EXECUTE format('ALTER USER custom_instance_user WITH PASSWORD %L', pwd);
RAISE NOTICE 'Updated password for existing user custom_instance_user';
ELSE
EXECUTE format('CREATE USER custom_instance_user WITH PASSWORD %L', pwd);
RAISE NOTICE 'Created new user custom_instance_user';
END IF;
IF NOT EXISTS (SELECT 1 FROM global_settings WHERE name = 'custom_instance_pg_databases') THEN
INSERT INTO global_settings (name, value)
VALUES ('custom_instance_pg_databases', jsonb_build_object(
'user_pwd', pwd::text,
'databases', jsonb_build_object()
));
RAISE NOTICE 'Inserted new global setting for custom_instance_pg_databases';
ELSE
UPDATE global_settings
SET value = jsonb_set(COALESCE(value, '{}'::jsonb), '{user_pwd}', to_jsonb(pwd::text)::jsonb)
WHERE name = 'custom_instance_pg_databases';
RAISE NOTICE 'Updated user_pwd in existing global setting for custom_instance_pg_databases';
END IF;
EXCEPTION
WHEN others THEN
RAISE NOTICE 'custom_instance_pg_databases migration error, skipping.';
END
$$;

View File

@@ -847,6 +847,20 @@ paths:
schema:
type: boolean
/settings/refresh_custom_instance_user_pwd:
post:
summary: Refreshes the password for the custom_instance_user
operationId: refreshCustomInstanceUserPwd
tags:
- setting
responses:
"200":
description: Success
content:
application/json:
schema:
type: object
/settings/list_custom_instance_pg_databases:
post:
summary: Returns the set-up statuses of custom instance pg databases

View File

@@ -73,6 +73,10 @@ pub fn global_service() -> Router {
"/list_custom_instance_pg_databases",
post(list_custom_instance_pg_databases),
)
.route(
"/refresh_custom_instance_user_pwd",
post(refresh_custom_instance_user_pwd),
)
.route(
"/setup_custom_instance_pg_database/:name",
post(setup_custom_instance_pg_database),
@@ -625,6 +629,47 @@ async fn list_custom_instance_pg_databases(
return Ok(Json(result));
}
async fn refresh_custom_instance_user_pwd(
authed: ApiAuthed,
Extension(db): Extension<DB>,
) -> JsonResult<()> {
require_super_admin(&db, &authed.email).await?;
// 20251208123907_safety_custom_instance_db_user_pwd.up
let query = r#"
DO $$
DECLARE
pwd text;
BEGIN
SELECT gen_random_uuid()::text INTO pwd;
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'custom_instance_user') THEN
EXECUTE format('ALTER USER custom_instance_user WITH PASSWORD %L', pwd);
RAISE NOTICE 'Updated password for existing user custom_instance_user';
ELSE
EXECUTE format('CREATE USER custom_instance_user WITH PASSWORD %L', pwd);
RAISE NOTICE 'Created new user custom_instance_user';
END IF;
IF NOT EXISTS (SELECT 1 FROM global_settings WHERE name = 'custom_instance_pg_databases') THEN
INSERT INTO global_settings (name, value)
VALUES ('custom_instance_pg_databases', jsonb_build_object(
'user_pwd', pwd::text,
'databases', jsonb_build_object()
));
RAISE NOTICE 'Inserted new global setting for custom_instance_pg_databases';
ELSE
UPDATE global_settings
SET value = jsonb_set(COALESCE(value, '{}'::jsonb), '{user_pwd}', to_jsonb(pwd::text)::jsonb)
WHERE name = 'custom_instance_pg_databases';
RAISE NOTICE 'Updated user_pwd in existing global setting for custom_instance_pg_databases';
END IF;
END
$$;
"#;
sqlx::query(query).execute(&db).await?;
Ok(Json(()))
}
#[derive(Deserialize)]
struct SetupCustomInstanceDbBody {
tag: Option<String>,

View File

@@ -79,11 +79,11 @@
"windmill-parser-wasm-java": "1.510.1",
"windmill-parser-wasm-nu": "1.510.1",
"windmill-parser-wasm-php": "1.574.1",
"windmill-parser-wasm-py": "1.538.0",
"windmill-parser-wasm-regex": "1.576.3",
"windmill-parser-wasm-py": "1.589.3",
"windmill-parser-wasm-regex": "1.589.3",
"windmill-parser-wasm-ruby": "1.526.1",
"windmill-parser-wasm-rust": "1.558.1",
"windmill-parser-wasm-ts": "1.565.0",
"windmill-parser-wasm-ts": "1.589.3",
"windmill-parser-wasm-yaml": "1.561.0",
"windmill-sql-datatype-parser-wasm": "1.512.0",
"windmill-utils-internal": "^1.3.1",
@@ -13851,14 +13851,14 @@
"integrity": "sha512-COyid6B1RYs+bpzUCInsA4HY/WZkpDLfkQ90+AqU/TVTpzYSbAC2JCbIwy0cRElBvlhI4bQ+9Wg6hSQKMpEkpA=="
},
"node_modules/windmill-parser-wasm-py": {
"version": "1.538.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.538.0.tgz",
"integrity": "sha512-s+bdIgT/fA5em3zYUwF8D14uA/dZh7iu0krZYZQqZUO7txN37hwSCVfovbMkIwm4zPbsJ50mU8DRLt7UpAPZIw=="
"version": "1.589.3",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-py/-/windmill-parser-wasm-py-1.589.3.tgz",
"integrity": "sha512-ob1auYfqDyjaliXVluSKAuhfW09TefJ1rJjfDBnV5KCumkTBYGSchrGU+2AJYiuwCtljRE5CzTPBAo6JTaSKHA=="
},
"node_modules/windmill-parser-wasm-regex": {
"version": "1.576.3",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.576.3.tgz",
"integrity": "sha512-+5or2Gzi2W+DXU3cDqUB2sn3t3t1ZJ5BGpEh94r30fbm9yGxSy9QvxsURqv59ubKCjmm3oKwqApSyn+OqXSLjg=="
"version": "1.589.3",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-regex/-/windmill-parser-wasm-regex-1.589.3.tgz",
"integrity": "sha512-yfYxHtrTuW+vqovPvu1h0t9XmC1cLzE6Quxb6fke3rxKYxQ3scO31zvIeZs2hGlvi/nzJjtCCgmZRyqD6RVQXQ=="
},
"node_modules/windmill-parser-wasm-ruby": {
"version": "1.526.1",
@@ -13871,9 +13871,9 @@
"integrity": "sha512-21S7lm1KF8zO1187rbq14hzPHII2RdM2+D44MoAh1F6VoaScj+Puq0z5B1O/hwn/95R/a9jBlL2D8jbkXtlD1A=="
},
"node_modules/windmill-parser-wasm-ts": {
"version": "1.565.0",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.565.0.tgz",
"integrity": "sha512-ui7dQ2kizWSG0ELpDU70Ccfk47EjC+yLI3tf6O+BdINs+YTjxA65MjShsvE3l4MnaxN8knbOLoSHkvIYCu77mg=="
"version": "1.589.3",
"resolved": "https://registry.npmjs.org/windmill-parser-wasm-ts/-/windmill-parser-wasm-ts-1.589.3.tgz",
"integrity": "sha512-PNyzEWo3qJxlocz1qmeh4IOxm8TzCeyAqqXFO4oyz9Lu6WAyCaSfefhZfaZG01wz3J1hJTZxymB9dDiv0VxgzQ=="
},
"node_modules/windmill-parser-wasm-yaml": {
"version": "1.561.0",

View File

@@ -24,11 +24,11 @@
"@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.86.2",
"@playwright/test": "^1.34.3",
"@tailwindcss/forms": "^0.5.3",
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.28.0",
"@sveltejs/package": "^2.3.7",
"@sveltejs/vite-plugin-svelte": "^6.1.1",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.8",
"@types/d3": "^7.4.0",
"@types/d3-zoom": "^3.0.3",
@@ -146,11 +146,11 @@
"windmill-parser-wasm-java": "1.510.1",
"windmill-parser-wasm-nu": "1.510.1",
"windmill-parser-wasm-php": "1.574.1",
"windmill-parser-wasm-py": "1.538.0",
"windmill-parser-wasm-regex": "1.576.3",
"windmill-parser-wasm-py": "1.589.3",
"windmill-parser-wasm-regex": "1.589.3",
"windmill-parser-wasm-ruby": "1.526.1",
"windmill-parser-wasm-rust": "1.558.1",
"windmill-parser-wasm-ts": "1.565.0",
"windmill-parser-wasm-ts": "1.589.3",
"windmill-parser-wasm-yaml": "1.561.0",
"windmill-sql-datatype-parser-wasm": "1.512.0",
"windmill-utils-internal": "^1.3.1",
@@ -543,4 +543,4 @@
"@rollup/rollup-linux-x64-gnu": "^4.35.0",
"fsevents": "^2.3.3"
}
}
}

View File

@@ -16,9 +16,11 @@
import type { ConfirmationModalHandle } from '../common/confirmationModal/asyncConfirmationModal.svelte'
import ExploreAssetButton from '../ExploreAssetButton.svelte'
import type DBManagerDrawer from '../DBManagerDrawer.svelte'
import { ArrowRight } from 'lucide-svelte'
import { ArrowRight, InfoIcon } from 'lucide-svelte'
import type { Snippet } from 'svelte'
import { truncate } from '$lib/utils'
import Tooltip from '../meltComponents/Tooltip.svelte'
import { superadmin } from '$lib/stores'
type Props = {
customInstanceDbs: ResourceReturn<ListCustomInstanceDbsResponse>
@@ -81,7 +83,7 @@
/>
</div>
</div>
<div class="flex-1 shrink-0 flex flex-col pl-4 gap-4">
<div class="flex-1 shrink-0 flex flex-col pl-4 gap-2">
<div class="flex-1 overflow-y-scroll">
{#if status?.error}
<div transition:slide={{ duration: 200 }} class="mb-4">
@@ -141,6 +143,20 @@
)}
/>
</div>
{#if $superadmin}
<Tooltip>
<Button
endIcon={{ icon: InfoIcon }}
onClick={async () => {
await SettingService.refreshCustomInstanceUserPwd()
sendUserToast('custom_instance_user password refreshed')
}}>Refresh custom_instance_user password</Button
>
{#snippet text()}
Try this if there is an issue with your custom instance database password.
{/snippet}
</Tooltip>
{/if}
<Button
size="sm"
variant={!status?.success ? 'accent' : 'default'}

View File

@@ -150,6 +150,10 @@
}
return map
})
export function unsavedChanges(): { savedValue: any; modifiedValue: any } {
return { savedValue: dataTableSettings, modifiedValue: tempSettings }
}
</script>
<div class="flex flex-col gap-4 my-8">
@@ -282,7 +286,7 @@
</tbody>
</DataTable>
<Button wrapperClasses="mt-4 mb-16 max-w-fit" on:click={onSave}>Save</Button>
<Button wrapperClasses="mt-4 mb-16 max-w-fit" on:click={onSave} variant="accent">Save</Button>
<ConfirmationModal {...confirmationModal.props} />
<DBManagerDrawer bind:this={dbManagerDrawer} />

View File

@@ -358,6 +358,7 @@
</DataTable>
<Button
wrapperClasses="mt-4 mb-16 max-w-fit"
variant="accent"
on:click={onSave}
disabled={ducklakeSavedSettings.ducklakes.length === ducklakeSettings.ducklakes.length &&
Object.values(ducklakeIsDirty).every((v) => v === false)}

View File

@@ -116,6 +116,7 @@
})
let dataTableSettings: DataTableSettingsType = $state({ dataTables: [] })
let dataTableSettingsComponent: DataTableSettings | undefined = $state(undefined)
let ducklakeSettings: DucklakeSettingsType = $state({ ducklakes: [] })
let ducklakeSavedSettings: DucklakeSettingsType = $state(untrack(() => ducklakeSettings))
@@ -559,6 +560,10 @@
// Combined function to check for unsaved changes across all tabs
function getAllUnsavedChanges() {
if (dataTableSettingsComponent) {
return dataTableSettingsComponent.unsavedChanges()
}
// Check AI settings
const aiChanges = getAiSettingsInitialAndModifiedValues()
if (aiChanges.savedValue && aiChanges.modifiedValue) {
@@ -1127,7 +1132,7 @@
}}
/>
{:else if tab == 'windmill_data_tables'}
<DataTableSettings bind:dataTableSettings />
<DataTableSettings bind:dataTableSettings bind:this={dataTableSettingsComponent} />
{:else if tab == 'windmill_lfs'}
<StorageSettings
bind:s3ResourceSettings