feat: expose row-level can_edit/can_delete on select *

Compute per-row editability and deletability from a table's row-level
security policies and return them as synthetic columns so clients can
hide edit/delete affordances for rows the user cannot change.

- Introspect pg_policies and relrowsecurity at schema-cache load and
  combine the UPDATE/DELETE USING qualifiers per table (permissive OR,
  restrictive AND).
- Store the combined qualifiers on Table and inject can_edit/can_delete
  as computed select fields when expanding `select *`, only for
  RLS-enabled tables with a matching policy (COALESCE'd to a boolean).
- Keep the computed columns out of the OpenAPI spec so they are not
  rendered as regular fields.
- Add a cfExpression field to CoercibleField to carry raw SQL
  expressions through the planner to SqlFragment.
This commit is contained in:
2026-08-29 11:30:21 +02:00
parent 77ab8f83ac
commit 7989108b0b
11 changed files with 329 additions and 14 deletions
+31
View File
@@ -268,3 +268,34 @@ $$ language sql;
create or replace function get_work_mem() returns text as $$
select current_setting('work_mem', true);
$$ language sql;
-- RLS fixtures for testing can_edit/can_delete computed fields
create table rls_items(
id int primary key,
account_id bigint,
name text
);
alter table rls_items enable row level security;
create policy rls_items_select on rls_items for select
using (
account_id is null
or (current_setting('request.jwt.claims', true)::json ->> 'account_id')::bigint = account_id
);
create policy rls_items_update on rls_items for update
using ((current_setting('request.jwt.claims', true)::json ->> 'account_id')::bigint = account_id);
create policy rls_items_delete on rls_items for delete
using ((current_setting('request.jwt.claims', true)::json ->> 'account_id')::bigint = account_id);
insert into rls_items(id, account_id, name) values (1, 1, 'own'), (2, null, 'public'), (3, 2, 'other');
-- no RLS at all: can_edit/can_delete must be omitted
create table no_rls_items(
id int primary key,
name text
);
insert into no_rls_items(id, name) values (1, 'a'), (2, 'b');