fix: apply token scopes to the runnables activity endpoint, refresh the owner-chips comment

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-10-09 09:55:15 +02:00
co-authored by Claude Opus 5.5
parent 7ab66ba097
commit aa97c53180
2 changed files with 18 additions and 13 deletions
+12 -10
View File
@@ -1149,26 +1149,28 @@ struct RunnablesActivityResponse {
/// The latest runs and the attached triggers of a batch of scripts and flows, for
/// the homepage rows. Only paths the caller can see as a deployed script or flow
/// are answered: the `visible` CTE runs under RLS, and `native_trigger`, which has
/// no RLS, is only reached through it.
/// no RLS, is only reached through it. RLS doesn't honor token scopes, so a path
/// outside the token's `scripts:read` / `flows:read` grant is dropped first.
async fn get_runnables_activity(
authed: ApiAuthed,
Extension(user_db): Extension<UserDB>,
Path(w_id): Path<String>,
Json(req): Json<RunnablesActivityRequest>,
) -> JsonResult<RunnablesActivityResponse> {
let mut paths: Vec<String> = vec![];
let mut is_flows: Vec<bool> = vec![];
for (list, is_flow) in [(req.scripts, false), (req.flows, true)] {
for p in list {
paths.push(p);
is_flows.push(is_flow);
}
}
if paths.len() > ACTIVITY_MAX_PATHS {
if req.scripts.len() + req.flows.len() > ACTIVITY_MAX_PATHS {
return Err(Error::BadRequest(format!(
"at most {ACTIVITY_MAX_PATHS} paths per request"
)));
}
let mut paths: Vec<String> = vec![];
let mut is_flows: Vec<bool> = vec![];
for (list, is_flow, domain) in [(req.scripts, false, "scripts"), (req.flows, true, "flows")] {
let scope = build_scope_path_filter(&authed, domain, "read");
for p in list.into_iter().filter(|p| scope.allows(p)) {
paths.push(p);
is_flows.push(is_flow);
}
}
let mut res = RunnablesActivityResponse::default();
if paths.is_empty() {
return Ok(Json(res));
@@ -2055,8 +2055,8 @@
</div>
</div>
{#if !contentActive && hasChips}
<!-- Owner and label chips on one line. Each function binding routes the chip's
selection into the searchbar key of the same name, and `queryName` points
<!-- Owner chips. The function binding routes the selected chip into the
searchbar's `owner` key, and `queryName` points
ListFilters' own mount-time URL read at the param the filter instance syncs, so
the two writers agree. No `syncQuery`: the filter instance owns the URL. -->
<div class="gap-2 w-full flex flex-wrap mt-3">
@@ -2234,7 +2234,10 @@
variant="subtle"
wrapperClasses="w-fit"
disabled={filter !== ''}
btnClasses={twMerge('-ml-1.5 px-1.5 text-2xs font-medium whitespace-nowrap', active ? 'text-primary' : 'text-secondary')}
btnClasses={twMerge(
'-ml-1.5 px-1.5 text-2xs font-medium whitespace-nowrap',
active ? 'text-primary' : 'text-secondary'
)}
endIcon={active ? { icon: sortOrder === asc ? ArrowUp : ArrowDown } : undefined}
title={filter !== ''
? 'Sorting is disabled while searching (results are ranked by relevance)'