mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix(datatables): serialize role catalog mutations, and state each helper's authorization contract
The catalog is one JSON document, so create, rename, enable and delete are all read-modify-write. Two concurrent creates read the same snapshot, both succeed in the cluster, and the second write drops the first — leaving a live Postgres login with a password nobody recorded, which is the exact state the delete path exists to prevent. Every mutation now runs in one transaction holding an advisory lock across the read, the cluster DDL and the write, so a lost update cannot happen and a failure rolls the whole thing back. The DDL helpers take that transaction rather than the pool, which is what makes the lock cover them. Their statements moved off `sqlx::raw_sql`: the simple protocol is only needed for genuinely multi-statement SQL, and its future is not `Send`, which an axum handler holding the transaction requires. Each of these is one statement anyway. The new cross-crate surface now says what callers must do. `read_role_catalog` returns plaintext credentials; `create`/`rename`/`set_login`/`drop_instance_role` and `converge_connect_grants` mutate cluster-wide state; `read_datatable_entry` reads a workspace's raw config. All of them are superadmin-gated by their current handlers, but nothing said so at the definition, which is where the next caller looks. Also: the roles table reloads after a failed login toggle instead of leaving it claiming a flip that did not land; the rename affordance is the design-system `Button`, not a raw one; and `resolve_datatable_pg_as_caller` drops a `role` parameter no caller ever filled — browsing resolves as the data table's default until the database manager grows a picker. Why role passwords stay a plain `String` while the instance user's password beside them is a `StringOrSecretRef`, asked three times across reviews: that one is a secret ref because an operator supplies it and may want it from their own backend, while these are minted here and never entered by anyone, so there is nothing for a ref to point at. Encrypting generated secrets at rest is a separate change that would take the replication password with it. Now said at the field. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012ti5HyeTikPMYyW8YSdiHR
This commit is contained in:
co-authored by
Claude Opus 5
parent
0f557ad408
commit
b2479e87fa
@@ -2457,13 +2457,14 @@ async fn resolve_datatable_pg_as_caller(
|
||||
authed: &ApiAuthed,
|
||||
w_id: &str,
|
||||
datatable_name: &str,
|
||||
role: Option<&str>,
|
||||
) -> Result<PgDatabase> {
|
||||
let db_resource = get_datatable_resource_from_db(
|
||||
db,
|
||||
w_id,
|
||||
datatable_name,
|
||||
role,
|
||||
// The data table's default role. Browsing has no way to name another one yet; when the
|
||||
// database manager grows a role picker it passes the pick through here.
|
||||
None,
|
||||
DatatableAccess::Authed(authed.to_authed_ref()),
|
||||
)
|
||||
.await?;
|
||||
@@ -2477,7 +2478,7 @@ async fn get_datatable_schema(
|
||||
w_id: &str,
|
||||
datatable_name: &str,
|
||||
) -> Result<SchemaMap> {
|
||||
let pg_db = resolve_datatable_pg_as_caller(db, authed, w_id, datatable_name, None).await?;
|
||||
let pg_db = resolve_datatable_pg_as_caller(db, authed, w_id, datatable_name).await?;
|
||||
|
||||
// Connect to the datatable database
|
||||
let (client, connection) = pg_db.connect(Some(db)).await?;
|
||||
@@ -2571,7 +2572,7 @@ async fn get_datatable_tables(
|
||||
w_id: &str,
|
||||
datatable_name: &str,
|
||||
) -> Result<TableListMap> {
|
||||
let pg_db = resolve_datatable_pg_as_caller(db, authed, w_id, datatable_name, None).await?;
|
||||
let pg_db = resolve_datatable_pg_as_caller(db, authed, w_id, datatable_name).await?;
|
||||
let (client, connection) = pg_db.connect(Some(db)).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
@@ -2649,7 +2650,7 @@ async fn get_datatable_table_columns(
|
||||
)));
|
||||
}
|
||||
|
||||
let pg_db = resolve_datatable_pg_as_caller(db, authed, w_id, datatable_name, None).await?;
|
||||
let pg_db = resolve_datatable_pg_as_caller(db, authed, w_id, datatable_name).await?;
|
||||
let (client, connection) = pg_db.connect(Some(db)).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
|
||||
Reference in New Issue
Block a user