mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 16:01:42 +00:00
fix: allow users to always discard their own drafts without write permission (#9659)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
@@ -276,7 +276,17 @@ async fn update_draft(
|
||||
) -> Result<Json<SaveDraftResponse>> {
|
||||
let email = &authed.email;
|
||||
let path = path.to_path();
|
||||
require_can_write_path(&authed, &db, &user_db, &w_id, kind, path).await?;
|
||||
// Saving a draft requires write permission on the underlying path. Deleting
|
||||
// (discarding) one's OWN draft does not: the email-scoped row belongs to the
|
||||
// authed user, so they can always discard it even after losing write access
|
||||
// to the underlying item (e.g. a draft-only item whose folder perms changed).
|
||||
// The DELETE below is scoped to `email = authed.email`, so it can only ever
|
||||
// touch the caller's own row. Legacy (NULL-email) rows aren't owned by anyone
|
||||
// — they keep the write gate.
|
||||
let is_own_discard = req.value.is_none() && !req.legacy;
|
||||
if !is_own_discard {
|
||||
require_can_write_path(&authed, &db, &user_db, &w_id, kind, path).await?;
|
||||
}
|
||||
|
||||
let applied_at = if let Some(value) = &req.value {
|
||||
// Secret variable values must never sit in `draft.value` in plaintext
|
||||
|
||||
@@ -167,14 +167,23 @@
|
||||
)
|
||||
}
|
||||
|
||||
// Why a row can't be deployed/discarded (drives the disabled-checkbox tooltip
|
||||
// and the Discard button's title). `undefined` ⇒ actionable.
|
||||
// Why a row can't be deployed (drives the disabled-checkbox tooltip).
|
||||
// `undefined` ⇒ actionable.
|
||||
function blockedReason(item: Row): string | undefined {
|
||||
if (!item.mine) return 'This draft belongs to another user'
|
||||
if (!item.can_write) return "You don't have write permission on this path"
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Why a row can't be discarded (drives the Discard button's title).
|
||||
// Discarding only removes the caller's own draft row, which they always own,
|
||||
// so — unlike deploy — it never requires write permission on the path. The
|
||||
// only block is someone else's draft (view-only in the "all drafts" view).
|
||||
function discardBlockedReason(item: Row): string | undefined {
|
||||
if (!item.mine) return 'This draft belongs to another user'
|
||||
return undefined
|
||||
}
|
||||
|
||||
// The Draft Items list only carries the *deployed* summary, so the draft's
|
||||
// (new) display name isn't known yet. Fetch each item's draft blob once and
|
||||
// cache both names — mirrors CompareWorkspaces' fetchSummaries (eager on load,
|
||||
@@ -628,7 +637,7 @@
|
||||
</Button>
|
||||
{/if}
|
||||
{:else}
|
||||
{@const discardBlock = blockedReason(draftItem)}
|
||||
{@const discardBlock = discardBlockedReason(draftItem)}
|
||||
<!-- Show diff fetches the *current user's* draft overlay, so it's only
|
||||
meaningful for your own/legacy rows. Another user's draft (view-only,
|
||||
`mine=false`) would diff against the wrong draft or 404 — hide it. -->
|
||||
|
||||
@@ -211,7 +211,10 @@
|
||||
}
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !canEdit,
|
||||
// A draft-only row is always the authed user's own draft (the
|
||||
// list endpoint only surfaces own/legacy draft-only rows), so
|
||||
// discarding it never requires write permission on the path.
|
||||
disabled: !showEditButton,
|
||||
hide: $userStore?.operator
|
||||
},
|
||||
{
|
||||
|
||||
@@ -230,7 +230,10 @@
|
||||
}
|
||||
},
|
||||
type: 'delete',
|
||||
disabled: !owner,
|
||||
// A draft-only row is always the authed user's own draft (the
|
||||
// list endpoint only surfaces own/legacy draft-only rows), so
|
||||
// discarding it never requires write permission on the path.
|
||||
disabled: !showEditButton,
|
||||
hide: $userStore?.operator
|
||||
}
|
||||
]
|
||||
|
||||
@@ -296,7 +296,10 @@
|
||||
}
|
||||
},
|
||||
type: dlt,
|
||||
disabled: !canEdit
|
||||
// A draft-only row is always the authed user's own draft (the
|
||||
// list endpoint only surfaces own/legacy draft-only rows), so
|
||||
// discarding it never requires write permission on the path.
|
||||
disabled: !showEditButton
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user