@selvajs/supabase-provider 0.20.0-beta.4 → 0.20.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +2 -0
  2. package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
  3. package/dist/auth/SupabaseAuthProvider.js +2 -8
  4. package/dist/auth/SupabaseAuthProvider.js.map +1 -1
  5. package/dist/data/SupabaseAuditQuery.js +1 -1
  6. package/dist/data/SupabaseAuditQuery.js.map +1 -1
  7. package/dist/data/SupabaseComputeServerStore.d.ts +5 -4
  8. package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
  9. package/dist/data/SupabaseComputeServerStore.js +53 -77
  10. package/dist/data/SupabaseComputeServerStore.js.map +1 -1
  11. package/dist/data/SupabaseDefinitionStore.js +7 -6
  12. package/dist/data/SupabaseDefinitionStore.js.map +1 -1
  13. package/dist/data/SupabaseInviteStore.d.ts +1 -1
  14. package/dist/data/SupabaseInviteStore.d.ts.map +1 -1
  15. package/dist/data/SupabaseInviteStore.js +3 -2
  16. package/dist/data/SupabaseInviteStore.js.map +1 -1
  17. package/dist/data/SupabaseOrgStore.d.ts.map +1 -1
  18. package/dist/data/SupabaseOrgStore.js +10 -34
  19. package/dist/data/SupabaseOrgStore.js.map +1 -1
  20. package/dist/data/SupabaseProjectStore.d.ts.map +1 -1
  21. package/dist/data/SupabaseProjectStore.js +15 -12
  22. package/dist/data/SupabaseProjectStore.js.map +1 -1
  23. package/dist/data/SupabaseShareLinkStore.d.ts.map +1 -1
  24. package/dist/data/SupabaseShareLinkStore.js +34 -29
  25. package/dist/data/SupabaseShareLinkStore.js.map +1 -1
  26. package/dist/data/SupabaseSolveMetricSink.d.ts +11 -3
  27. package/dist/data/SupabaseSolveMetricSink.d.ts.map +1 -1
  28. package/dist/data/SupabaseSolveMetricSink.js +38 -14
  29. package/dist/data/SupabaseSolveMetricSink.js.map +1 -1
  30. package/dist/data/errors.d.ts.map +1 -1
  31. package/dist/data/errors.js +3 -0
  32. package/dist/data/errors.js.map +1 -1
  33. package/dist/data/migrationHead.d.ts +1 -1
  34. package/dist/data/migrationHead.js +1 -1
  35. package/dist/data/pagination.d.ts +2 -3
  36. package/dist/data/pagination.d.ts.map +1 -1
  37. package/dist/data/pagination.js +6 -15
  38. package/dist/data/pagination.js.map +1 -1
  39. package/dist/permissions/SupabasePlatformPermissionStore.d.ts +2 -2
  40. package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -1
  41. package/dist/permissions/SupabasePlatformPermissionStore.js +10 -49
  42. package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -1
  43. package/dist/storage/SupabaseStorageProvider.d.ts +3 -2
  44. package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -1
  45. package/dist/storage/SupabaseStorageProvider.js +30 -19
  46. package/dist/storage/SupabaseStorageProvider.js.map +1 -1
  47. package/dist/userProfile/SupabaseUserProfileProvider.d.ts +7 -0
  48. package/dist/userProfile/SupabaseUserProfileProvider.d.ts.map +1 -1
  49. package/dist/userProfile/SupabaseUserProfileProvider.js +9 -19
  50. package/dist/userProfile/SupabaseUserProfileProvider.js.map +1 -1
  51. package/package.json +4 -3
  52. package/supabase/migrations/20261101152228_selva_revoke_privileged_functions.sql +44 -0
  53. package/supabase/migrations/20261101160000_selva_rls_is_the_boundary.sql +527 -0
  54. package/supabase/migrations/20261101170000_selva_compute_api_key_private.sql +14 -0
  55. package/supabase/migrations/20261101180000_selva_erasure_keeps_tenant_data.sql +28 -0
  56. package/supabase/migrations/20261101190000_selva_atomic_compute_save.sql +136 -0
  57. package/supabase/migrations/20261101200000_selva_last_admin_lock_order.sql +62 -0
@@ -0,0 +1,136 @@
1
+ -- ============================================================================
2
+ -- A compute save lands whole or not at all
3
+ --
4
+ -- Security invoker, so the existing policies still decide who may write. Rows
5
+ -- that stay are updated rather than deleted and reinserted: deleting clears
6
+ -- `compute_server_org_defaults` through its FK. The update comes before an
7
+ -- `on conflict do nothing` insert because reading `excluded.api_key` would need
8
+ -- the SELECT that sessions no longer have on that column.
9
+ -- ============================================================================
10
+
11
+ create or replace function selva.save_platform_compute_servers(
12
+ servers jsonb,
13
+ shares jsonb,
14
+ default_server uuid
15
+ )
16
+ returns void
17
+ language plpgsql set search_path = public
18
+ as $$
19
+ declare
20
+ submitted integer := jsonb_array_length(servers);
21
+ saved integer;
22
+ added integer;
23
+ begin
24
+ delete from selva.compute_servers c
25
+ where c.scope = 'platform'
26
+ and c.id not in (select (s ->> 'id')::uuid from jsonb_array_elements(servers) s);
27
+
28
+ update selva.compute_servers c
29
+ set label = x.label, server_url = x.server_url, api_key = x.api_key,
30
+ timeout_ms = x.timeout_ms, retry_count = x.retry_count,
31
+ shared_with_all = x.shared_with_all
32
+ from jsonb_to_recordset(servers) as x(
33
+ id uuid, label text, server_url text, api_key text,
34
+ timeout_ms integer, retry_count integer, shared_with_all boolean
35
+ )
36
+ where c.id = x.id and c.scope = 'platform';
37
+ get diagnostics saved = row_count;
38
+
39
+ insert into selva.compute_servers
40
+ (id, scope, label, server_url, api_key, timeout_ms, retry_count, shared_with_all)
41
+ select x.id, 'platform', x.label, x.server_url, x.api_key, x.timeout_ms, x.retry_count,
42
+ x.shared_with_all
43
+ from jsonb_to_recordset(servers) as x(
44
+ id uuid, label text, server_url text, api_key text,
45
+ timeout_ms integer, retry_count integer, shared_with_all boolean
46
+ )
47
+ on conflict (id) do nothing;
48
+ get diagnostics added = row_count;
49
+
50
+ -- The insert skips what the update handled, so the two only fall short on a
51
+ -- collision.
52
+ if saved + added < submitted then
53
+ raise exception 'A compute server id is already in use' using errcode = '23505';
54
+ end if;
55
+
56
+ delete from selva.compute_server_shares cs
57
+ using selva.compute_servers c
58
+ where c.id = cs.server_id and c.scope = 'platform';
59
+
60
+ insert into selva.compute_server_shares (server_id, org_id)
61
+ select x.server_id, x.org_id
62
+ from jsonb_to_recordset(shares) as x(server_id uuid, org_id uuid);
63
+
64
+ update selva.compute_server_platform_default
65
+ set default_server_id = default_server
66
+ where singleton;
67
+ end;
68
+ $$;
69
+
70
+ -- `set_default` is false to leave the org's default as it stands, whatever
71
+ -- `default_server` holds; with it true, a null `default_server` clears it.
72
+ create or replace function selva.save_org_compute_servers(
73
+ o uuid,
74
+ servers jsonb,
75
+ set_default boolean,
76
+ default_server uuid
77
+ )
78
+ returns void
79
+ language plpgsql set search_path = public
80
+ as $$
81
+ declare
82
+ submitted integer := jsonb_array_length(servers);
83
+ saved integer;
84
+ added integer;
85
+ begin
86
+ delete from selva.compute_servers c
87
+ where c.scope = 'org' and c.owner_org_id = o
88
+ and c.id not in (select (s ->> 'id')::uuid from jsonb_array_elements(servers) s);
89
+
90
+ update selva.compute_servers c
91
+ set label = x.label, server_url = x.server_url, api_key = x.api_key,
92
+ timeout_ms = x.timeout_ms, retry_count = x.retry_count
93
+ from jsonb_to_recordset(servers) as x(
94
+ id uuid, label text, server_url text, api_key text,
95
+ timeout_ms integer, retry_count integer
96
+ )
97
+ where c.id = x.id and c.scope = 'org' and c.owner_org_id = o;
98
+ get diagnostics saved = row_count;
99
+
100
+ insert into selva.compute_servers
101
+ (id, scope, owner_org_id, label, server_url, api_key, timeout_ms, retry_count)
102
+ select x.id, 'org', o, x.label, x.server_url, x.api_key, x.timeout_ms, x.retry_count
103
+ from jsonb_to_recordset(servers) as x(
104
+ id uuid, label text, server_url text, api_key text,
105
+ timeout_ms integer, retry_count integer
106
+ )
107
+ on conflict (id) do nothing;
108
+ get diagnostics added = row_count;
109
+
110
+ -- A colliding id belongs to another org or the platform, and this says no more
111
+ -- than that: the session may not be able to see the row.
112
+ if saved + added < submitted then
113
+ raise exception 'A compute server id is already in use' using errcode = '23505';
114
+ end if;
115
+
116
+ if set_default then
117
+ if default_server is null then
118
+ delete from selva.compute_server_org_defaults where org_id = o;
119
+ else
120
+ insert into selva.compute_server_org_defaults (org_id, default_server_id)
121
+ values (o, default_server)
122
+ on conflict (org_id) do update set default_server_id = excluded.default_server_id;
123
+ end if;
124
+ end if;
125
+ end;
126
+ $$;
127
+
128
+ revoke all on function
129
+ selva.save_platform_compute_servers(jsonb, jsonb, uuid),
130
+ selva.save_org_compute_servers(uuid, jsonb, boolean, uuid)
131
+ from public, anon;
132
+
133
+ grant execute on function
134
+ selva.save_platform_compute_servers(jsonb, jsonb, uuid),
135
+ selva.save_org_compute_servers(uuid, jsonb, boolean, uuid)
136
+ to authenticated, service_role;
@@ -0,0 +1,62 @@
1
+ -- ============================================================================
2
+ -- Concurrent permission writes queue instead of deadlocking
3
+ --
4
+ -- Each call locks its target, then the surviving admins, so two admins demoting
5
+ -- each other hold what the other wants. Postgres kills one and that caller gets
6
+ -- a 40P01 rather than `last_admin`. The advisory lock lets one call run at a
7
+ -- time, which is cheap: permission writes are rare.
8
+ -- ============================================================================
9
+
10
+ create or replace function selva.set_platform_permissions(
11
+ p_user_id uuid,
12
+ p_permissions text[]
13
+ )
14
+ returns integer
15
+ language plpgsql security definer set search_path = public
16
+ as $$
17
+ declare
18
+ v_is_demotion boolean;
19
+ v_others integer;
20
+ v_updated integer;
21
+ begin
22
+ perform pg_advisory_xact_lock(hashtext('selva.set_platform_permissions'));
23
+
24
+ select 'instance_admin' = any(platform_permissions)
25
+ and not ('instance_admin' = any(p_permissions))
26
+ into v_is_demotion
27
+ from selva.user_profiles
28
+ where user_id = p_user_id
29
+ for update;
30
+
31
+ if not found then
32
+ return 0;
33
+ end if;
34
+
35
+ if v_is_demotion then
36
+ -- Row locks still guard against `disabled` changing underneath the count.
37
+ perform 1
38
+ from selva.user_profiles
39
+ where user_id <> p_user_id
40
+ and disabled = false
41
+ and 'instance_admin' = any(platform_permissions)
42
+ for update;
43
+
44
+ select count(*) into v_others
45
+ from selva.user_profiles
46
+ where user_id <> p_user_id
47
+ and disabled = false
48
+ and 'instance_admin' = any(platform_permissions);
49
+
50
+ if v_others = 0 then
51
+ return 0;
52
+ end if;
53
+ end if;
54
+
55
+ update selva.user_profiles
56
+ set platform_permissions = p_permissions
57
+ where user_id = p_user_id;
58
+
59
+ get diagnostics v_updated = row_count;
60
+ return v_updated;
61
+ end;
62
+ $$;