@selvajs/supabase-provider 0.14.4-beta.0 → 0.15.0-beta.2

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 (158) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -0
  3. package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
  4. package/dist/auth/SupabaseAuthProvider.js +7 -14
  5. package/dist/auth/SupabaseAuthProvider.js.map +1 -1
  6. package/dist/data/SupabaseComputeServerStore.d.ts +14 -3
  7. package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
  8. package/dist/data/SupabaseComputeServerStore.js +67 -16
  9. package/dist/data/SupabaseComputeServerStore.js.map +1 -1
  10. package/dist/data/SupabaseDataProvider.d.ts +36 -8
  11. package/dist/data/SupabaseDataProvider.d.ts.map +1 -1
  12. package/dist/data/SupabaseDataProvider.js +119 -15
  13. package/dist/data/SupabaseDataProvider.js.map +1 -1
  14. package/dist/data/SupabaseDefinitionStore.d.ts +1 -0
  15. package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -1
  16. package/dist/data/SupabaseDefinitionStore.js +41 -4
  17. package/dist/data/SupabaseDefinitionStore.js.map +1 -1
  18. package/dist/data/SupabaseEventSink.d.ts +5 -2
  19. package/dist/data/SupabaseEventSink.d.ts.map +1 -1
  20. package/dist/data/SupabaseEventSink.js +20 -3
  21. package/dist/data/SupabaseEventSink.js.map +1 -1
  22. package/dist/data/SupabaseSolveMetricSink.d.ts +49 -4
  23. package/dist/data/SupabaseSolveMetricSink.d.ts.map +1 -1
  24. package/dist/data/SupabaseSolveMetricSink.js +109 -19
  25. package/dist/data/SupabaseSolveMetricSink.js.map +1 -1
  26. package/dist/data/index.d.ts +1 -0
  27. package/dist/data/index.d.ts.map +1 -1
  28. package/dist/data/index.js +1 -0
  29. package/dist/data/index.js.map +1 -1
  30. package/dist/data/migrationHead.d.ts +14 -0
  31. package/dist/data/migrationHead.d.ts.map +1 -0
  32. package/dist/data/migrationHead.js +14 -0
  33. package/dist/data/migrationHead.js.map +1 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/permissions/SupabasePlatformPermissionStore.d.ts +5 -3
  39. package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -1
  40. package/dist/permissions/SupabasePlatformPermissionStore.js +17 -30
  41. package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -1
  42. package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -1
  43. package/dist/storage/SupabaseStorageProvider.js +3 -0
  44. package/dist/storage/SupabaseStorageProvider.js.map +1 -1
  45. package/package.json +23 -14
  46. package/supabase/migrations/20260712120000_user_profiles_disabled_and_gin.sql +95 -0
  47. package/supabase/migrations/20260712123000_selva_migration_head_fn.sql +38 -0
  48. package/supabase/migrations/20260713120000_selva_next_version_number.sql +59 -0
  49. package/supabase/migrations/20260713140000_selva_solve_cache_limit.sql +15 -0
  50. package/supabase/migrations/20260716120000_definition_status_and_audit_version.sql +36 -0
  51. package/supabase/migrations/20260716130000_erase_user_audit_email.sql +36 -0
  52. package/supabase/migrations/20260717120000_compute_server_has_api_key.sql +20 -0
  53. package/dist/auth/__tests__/conformance.test.d.ts +0 -2
  54. package/dist/auth/__tests__/conformance.test.d.ts.map +0 -1
  55. package/dist/auth/__tests__/conformance.test.js +0 -76
  56. package/dist/auth/__tests__/conformance.test.js.map +0 -1
  57. package/dist/auth/__tests__/token-verification.test.d.ts +0 -13
  58. package/dist/auth/__tests__/token-verification.test.d.ts.map +0 -1
  59. package/dist/auth/__tests__/token-verification.test.js +0 -135
  60. package/dist/auth/__tests__/token-verification.test.js.map +0 -1
  61. package/dist/data/__tests__/compute-server-conformance.test.d.ts +0 -2
  62. package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +0 -1
  63. package/dist/data/__tests__/compute-server-conformance.test.js +0 -45
  64. package/dist/data/__tests__/compute-server-conformance.test.js.map +0 -1
  65. package/dist/data/__tests__/compute-server-encryption.test.d.ts +0 -12
  66. package/dist/data/__tests__/compute-server-encryption.test.d.ts.map +0 -1
  67. package/dist/data/__tests__/compute-server-encryption.test.js +0 -143
  68. package/dist/data/__tests__/compute-server-encryption.test.js.map +0 -1
  69. package/dist/data/__tests__/definition-conformance.test.d.ts +0 -2
  70. package/dist/data/__tests__/definition-conformance.test.d.ts.map +0 -1
  71. package/dist/data/__tests__/definition-conformance.test.js +0 -85
  72. package/dist/data/__tests__/definition-conformance.test.js.map +0 -1
  73. package/dist/data/__tests__/event-sink-conformance.test.d.ts +0 -2
  74. package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +0 -1
  75. package/dist/data/__tests__/event-sink-conformance.test.js +0 -27
  76. package/dist/data/__tests__/event-sink-conformance.test.js.map +0 -1
  77. package/dist/data/__tests__/invite-conformance.test.d.ts +0 -2
  78. package/dist/data/__tests__/invite-conformance.test.d.ts.map +0 -1
  79. package/dist/data/__tests__/invite-conformance.test.js +0 -45
  80. package/dist/data/__tests__/invite-conformance.test.js.map +0 -1
  81. package/dist/data/__tests__/org-conformance.test.d.ts +0 -2
  82. package/dist/data/__tests__/org-conformance.test.d.ts.map +0 -1
  83. package/dist/data/__tests__/org-conformance.test.js +0 -29
  84. package/dist/data/__tests__/org-conformance.test.js.map +0 -1
  85. package/dist/data/__tests__/project-conformance.test.d.ts +0 -2
  86. package/dist/data/__tests__/project-conformance.test.d.ts.map +0 -1
  87. package/dist/data/__tests__/project-conformance.test.js +0 -57
  88. package/dist/data/__tests__/project-conformance.test.js.map +0 -1
  89. package/dist/data/__tests__/share-link-concurrency.test.d.ts +0 -22
  90. package/dist/data/__tests__/share-link-concurrency.test.d.ts.map +0 -1
  91. package/dist/data/__tests__/share-link-concurrency.test.js +0 -115
  92. package/dist/data/__tests__/share-link-concurrency.test.js.map +0 -1
  93. package/dist/data/__tests__/share-link-conformance.test.d.ts +0 -2
  94. package/dist/data/__tests__/share-link-conformance.test.d.ts.map +0 -1
  95. package/dist/data/__tests__/share-link-conformance.test.js +0 -88
  96. package/dist/data/__tests__/share-link-conformance.test.js.map +0 -1
  97. package/dist/data/__tests__/solve-metric-sink-conformance.test.d.ts +0 -2
  98. package/dist/data/__tests__/solve-metric-sink-conformance.test.d.ts.map +0 -1
  99. package/dist/data/__tests__/solve-metric-sink-conformance.test.js +0 -52
  100. package/dist/data/__tests__/solve-metric-sink-conformance.test.js.map +0 -1
  101. package/dist/data/__tests__/test-helpers.d.ts +0 -52
  102. package/dist/data/__tests__/test-helpers.d.ts.map +0 -1
  103. package/dist/data/__tests__/test-helpers.js +0 -177
  104. package/dist/data/__tests__/test-helpers.js.map +0 -1
  105. package/dist/permissions/__tests__/conformance.test.d.ts +0 -2
  106. package/dist/permissions/__tests__/conformance.test.d.ts.map +0 -1
  107. package/dist/permissions/__tests__/conformance.test.js +0 -27
  108. package/dist/permissions/__tests__/conformance.test.js.map +0 -1
  109. package/dist/storage/__tests__/conformance.test.d.ts +0 -2
  110. package/dist/storage/__tests__/conformance.test.d.ts.map +0 -1
  111. package/dist/storage/__tests__/conformance.test.js +0 -149
  112. package/dist/storage/__tests__/conformance.test.js.map +0 -1
  113. package/dist/userProfile/__tests__/conformance.test.d.ts +0 -2
  114. package/dist/userProfile/__tests__/conformance.test.d.ts.map +0 -1
  115. package/dist/userProfile/__tests__/conformance.test.js +0 -38
  116. package/dist/userProfile/__tests__/conformance.test.js.map +0 -1
  117. package/src/auth/SupabaseAuthProvider.ts +0 -600
  118. package/src/auth/__tests__/conformance.test.ts +0 -78
  119. package/src/auth/__tests__/token-verification.test.ts +0 -158
  120. package/src/auth/index.ts +0 -2
  121. package/src/cli/sync-migrations.ts +0 -133
  122. package/src/data/SupabaseAuditQuery.ts +0 -103
  123. package/src/data/SupabaseComputeServerStore.ts +0 -382
  124. package/src/data/SupabaseDataProvider.ts +0 -153
  125. package/src/data/SupabaseDefinitionStore.ts +0 -527
  126. package/src/data/SupabaseEventSink.ts +0 -30
  127. package/src/data/SupabaseInviteStore.ts +0 -171
  128. package/src/data/SupabaseOrgStore.ts +0 -508
  129. package/src/data/SupabaseProjectStore.ts +0 -431
  130. package/src/data/SupabaseShareLinkStore.ts +0 -176
  131. package/src/data/SupabaseSolveMetricSink.ts +0 -38
  132. package/src/data/__tests__/compute-server-conformance.test.ts +0 -46
  133. package/src/data/__tests__/compute-server-encryption.test.ts +0 -173
  134. package/src/data/__tests__/definition-conformance.test.ts +0 -89
  135. package/src/data/__tests__/event-sink-conformance.test.ts +0 -32
  136. package/src/data/__tests__/invite-conformance.test.ts +0 -46
  137. package/src/data/__tests__/org-conformance.test.ts +0 -30
  138. package/src/data/__tests__/project-conformance.test.ts +0 -57
  139. package/src/data/__tests__/share-link-concurrency.test.ts +0 -124
  140. package/src/data/__tests__/share-link-conformance.test.ts +0 -87
  141. package/src/data/__tests__/solve-metric-sink-conformance.test.ts +0 -59
  142. package/src/data/__tests__/test-helpers.ts +0 -188
  143. package/src/data/client.ts +0 -173
  144. package/src/data/errors.ts +0 -45
  145. package/src/data/index.ts +0 -29
  146. package/src/data/pagination.ts +0 -68
  147. package/src/data/query.ts +0 -22
  148. package/src/data/rowStamp.ts +0 -48
  149. package/src/index.ts +0 -42
  150. package/src/permissions/SupabasePlatformPermissionStore.ts +0 -161
  151. package/src/permissions/__tests__/conformance.test.ts +0 -28
  152. package/src/permissions/index.ts +0 -1
  153. package/src/storage/SupabaseStorageProvider.ts +0 -182
  154. package/src/storage/__tests__/conformance.test.ts +0 -152
  155. package/src/storage/index.ts +0 -2
  156. package/src/userProfile/SupabaseUserProfileProvider.ts +0 -177
  157. package/src/userProfile/__tests__/conformance.test.ts +0 -39
  158. package/src/userProfile/index.ts +0 -1
@@ -0,0 +1,95 @@
1
+ -- ============================================================================
2
+ -- user_profiles.disabled denormalization + platform_permissions GIN index
3
+ --
4
+ -- The instance-admin invariant queries ("is there an enabled instance_admin?",
5
+ -- "how many enabled admins besides X?") previously selected candidate ids and
6
+ -- then called the GoTrue admin API once per candidate just to read
7
+ -- user_metadata.disabled — an N+1 on the bootstrap and admin-write paths.
8
+ --
9
+ -- auth.users.raw_user_meta_data->>'disabled' stays the source of truth (the
10
+ -- auth provider keeps writing it via the admin API); user_profiles.disabled is
11
+ -- a trigger-maintained read-model copy so the invariant collapses to a single
12
+ -- indexed query. The GIN index serves the `platform_permissions @> ...`
13
+ -- filters those queries already used.
14
+ -- ============================================================================
15
+
16
+ alter table selva.user_profiles
17
+ add column if not exists disabled boolean not null default false;
18
+
19
+ -- Signup seed: carry disabled state for users created already-disabled (e.g.
20
+ -- via the Supabase dashboard). Conflict semantics unchanged (do nothing) —
21
+ -- a profile row can only pre-exist on re-fired triggers, where the update
22
+ -- trigger below owns the sync.
23
+ create or replace function selva.handle_new_auth_user()
24
+ returns trigger
25
+ language plpgsql
26
+ security definer
27
+ set search_path = public
28
+ as $$
29
+ begin
30
+ insert into selva.user_profiles (user_id, disabled)
31
+ values (new.id, coalesce((new.raw_user_meta_data ->> 'disabled')::boolean, false))
32
+ on conflict (user_id) do nothing;
33
+ return new;
34
+ end;
35
+ $$;
36
+
37
+ -- Sync disable/enable flips. The original mirror trigger is INSERT-only, so
38
+ -- without this the read-model copy would never see a later flip (which lands
39
+ -- as an UPDATE of raw_user_meta_data through the GoTrue admin API).
40
+ create or replace function selva.sync_auth_user_disabled()
41
+ returns trigger
42
+ language plpgsql
43
+ security definer
44
+ set search_path = public
45
+ as $$
46
+ begin
47
+ update selva.user_profiles
48
+ set disabled = coalesce((new.raw_user_meta_data ->> 'disabled')::boolean, false)
49
+ where user_id = new.id;
50
+ return new;
51
+ end;
52
+ $$;
53
+
54
+ drop trigger if exists trg_auth_user_disabled_sync on auth.users;
55
+ create trigger trg_auth_user_disabled_sync
56
+ after update of raw_user_meta_data on auth.users
57
+ for each row
58
+ when (
59
+ coalesce(old.raw_user_meta_data ->> 'disabled', 'false')
60
+ is distinct from coalesce(new.raw_user_meta_data ->> 'disabled', 'false')
61
+ )
62
+ execute function selva.sync_auth_user_disabled();
63
+
64
+ -- Backfill existing rows from the source of truth.
65
+ update selva.user_profiles p
66
+ set disabled = coalesce((u.raw_user_meta_data ->> 'disabled')::boolean, false)
67
+ from auth.users u
68
+ where u.id = p.user_id
69
+ and p.disabled is distinct from coalesce((u.raw_user_meta_data ->> 'disabled')::boolean, false);
70
+
71
+ -- The `platform_permissions @> '{instance_admin}'` filters in the permission
72
+ -- store were sequential scans without this.
73
+ create index if not exists idx_user_profiles_platform_permissions
74
+ on selva.user_profiles using gin (platform_permissions);
75
+
76
+ -- Extend the self-update RLS policy: users must not be able to clear their own
77
+ -- disabled flag (same freeze pattern as platform_permissions — only
78
+ -- service-role writes and the sync trigger may change it).
79
+ drop policy if exists "user_profiles: users can update own" on selva.user_profiles;
80
+
81
+ create policy "user_profiles: users can update own"
82
+ on selva.user_profiles for update
83
+ to authenticated
84
+ using (user_id = auth.uid())
85
+ with check (
86
+ user_id = auth.uid()
87
+ -- Users cannot escalate their own platform permissions; only service-role
88
+ -- writes (which bypass RLS) may change that column.
89
+ and platform_permissions is not distinct from (
90
+ select platform_permissions from selva.user_profiles where user_id = auth.uid()
91
+ )
92
+ and disabled is not distinct from (
93
+ select disabled from selva.user_profiles where user_id = auth.uid()
94
+ )
95
+ );
@@ -0,0 +1,38 @@
1
+ -- ============================================================================
2
+ -- selva.migration_head() — app↔DB schema handshake (audit O3)
3
+ --
4
+ -- The app has no way to see whether the operator applied this package's
5
+ -- migrations: self-update only runs `npm install` + pm2 restart, and PostgREST
6
+ -- does not expose `supabase_migrations`. This SECURITY DEFINER function makes
7
+ -- the migration head queryable through the `selva` schema the app is pinned
8
+ -- to. The app compares it against its compiled-in EXPECTED_MIGRATION_HEAD at
9
+ -- boot and degrades /api/health on skew — which also makes the self-update
10
+ -- runner's health probe auto-roll-back an update whose migrations weren't
11
+ -- applied yet.
12
+ --
13
+ -- Bootstrapping is intentional: on a database that never applied THIS
14
+ -- migration the RPC itself is missing, which the app reads as "schema behind".
15
+ -- ============================================================================
16
+
17
+ create or replace function selva.migration_head()
18
+ returns text
19
+ language plpgsql
20
+ security definer
21
+ set search_path = ''
22
+ stable
23
+ as $$
24
+ begin
25
+ return coalesce(
26
+ (select max(version) from supabase_migrations.schema_migrations),
27
+ ''
28
+ );
29
+ exception
30
+ -- A project where the CLI never pushed migrations has no
31
+ -- supabase_migrations.schema_migrations table at all.
32
+ when undefined_table then
33
+ return '';
34
+ end;
35
+ $$;
36
+
37
+ revoke all on function selva.migration_head() from public;
38
+ grant execute on function selva.migration_head() to service_role;
@@ -0,0 +1,59 @@
1
+ -- ============================================================================
2
+ -- Monotonic per-definition version-number allocator (audit DC / caching Phase 1.5)
3
+ -- ============================================================================
4
+ --
5
+ -- Root-cause fix for `fileKey` reuse: `uploadVersion` used to compute the next
6
+ -- version number as max(existing)+1, so deleting the latest version freed its
7
+ -- number and the next upload reused it — reusing the `fileKey`
8
+ -- (`versions/v{N}.gh`) and letting a stale storage blob (or any layer keying on
9
+ -- the key) serve the old version's bytes for new content.
10
+ --
11
+ -- The `next_version_number` counter lives on the definition row and only ever
12
+ -- advances. `reserve_next_version_number` returns the current value and bumps it
13
+ -- atomically, so concurrent uploads never collide and a deleted version's number
14
+ -- is never handed out again.
15
+
16
+ -- Column: defaults to 2 (v1 is created with the parent, so the next upload takes
17
+ -- 2). Backfilled below for any pre-existing rows.
18
+ alter table selva.definitions
19
+ add column if not exists next_version_number integer not null default 2;
20
+
21
+ -- Backfill existing definitions to one past their highest live version number,
22
+ -- so already-uploaded versions don't get their numbers reused. Idempotent: after
23
+ -- the first run the counter is already >= this, and greatest() keeps it from
24
+ -- ever moving backwards.
25
+ update selva.definitions d
26
+ set next_version_number = greatest(
27
+ d.next_version_number,
28
+ coalesce(
29
+ (select max(v.version_number) + 1
30
+ from selva.definition_versions v
31
+ where v.definition_guid = d.guid),
32
+ 2
33
+ )
34
+ );
35
+
36
+ -- Atomic reserve-and-increment. Returns the reserved number (the value BEFORE
37
+ -- the bump) and advances the counter. Never decrements. Raises if the row is
38
+ -- missing or soft-deleted so the caller surfaces a 404 rather than silently
39
+ -- minting a number for a dead definition.
40
+ create or replace function selva.reserve_next_version_number(g uuid)
41
+ returns integer
42
+ language plpgsql security definer set search_path = public
43
+ as $$
44
+ declare
45
+ reserved integer;
46
+ begin
47
+ update selva.definitions
48
+ set next_version_number = next_version_number + 1
49
+ where guid = g and deleted_at is null
50
+ returning next_version_number - 1 into reserved;
51
+
52
+ if reserved is null then
53
+ raise exception 'definition % not found', g using errcode = 'no_data_found';
54
+ end if;
55
+
56
+ return reserved;
57
+ end;
58
+ $$;
59
+ grant execute on function selva.reserve_next_version_number(uuid) to authenticated, service_role;
@@ -0,0 +1,15 @@
1
+ -- ============================================================================
2
+ -- Per-definition durable solve-cache quota (caching Phase 3 / R9)
3
+ -- ============================================================================
4
+ --
5
+ -- `solve_cache_limit` is the definition's L2 solve-cache policy, one number:
6
+ -- NULL → inherit the global default (SOLVE_CACHE_DEFAULT_MAX_ENTRIES)
7
+ -- 0 → caching off for this definition (non-determinism / wide-input escape hatch)
8
+ -- N → keep at most N cached solves for this definition
9
+ --
10
+ -- Nullable, no default: an absent value MEANS inherit, so existing rows need no
11
+ -- backfill — they simply inherit the global default until an author sets a value.
12
+
13
+ alter table selva.definitions
14
+ add column if not exists solve_cache_limit integer
15
+ check (solve_cache_limit is null or solve_cache_limit >= 0);
@@ -0,0 +1,36 @@
1
+ -- ============================================================================
2
+ -- Definition-status enum cleanup (audit D2) + audit-event versioning (audit D3)
3
+ -- ============================================================================
4
+
5
+ -- D2. The definition-status CHECK admitted five values, but only four are real:
6
+ -- pending — metadata-first create window (DefinitionService.create writes it,
7
+ -- a partial reclaim index tracks it). REAL — kept.
8
+ -- draft — work in progress. REAL — kept.
9
+ -- published — live. REAL — kept.
10
+ -- archived — retired but preserved. REAL — kept.
11
+ -- review — never written or read anywhere in the codebase. DEAD — dropped.
12
+ -- Dropping `review` while the table holds no such rows is a zero-row change; do
13
+ -- it now, while it is a one-line CHECK swap rather than a data migration later.
14
+ -- (Postgres has no "alter check" — drop the old constraint and add the new one.
15
+ -- The constraint name is the Postgres-generated default `definitions_status_check`.)
16
+
17
+ alter table selva.definitions
18
+ drop constraint if exists definitions_status_check;
19
+
20
+ alter table selva.definitions
21
+ add constraint definitions_status_check
22
+ check (status in ('pending', 'draft', 'published', 'archived'));
23
+
24
+ -- D3. `audit_events` is append-only forever: every row written before a version
25
+ -- marker exists is a row whose payload shape must be inferred later. Add an
26
+ -- explicit `event_version` column now so the `data` envelope carries its schema
27
+ -- version out of band. Existing rows are the v1 shape by definition, so default
28
+ -- + backfill to 1; NOT NULL once populated.
29
+
30
+ alter table selva.audit_events
31
+ add column if not exists event_version integer not null default 1;
32
+
33
+ comment on column selva.audit_events.event_version is
34
+ 'Schema version of the `data` payload (the DomainEvent envelope). Bump when '
35
+ 'the persisted shape changes so readers can dispatch on version instead of '
36
+ 'inferring it. v1 = the initial DomainEvent union.';
@@ -0,0 +1,36 @@
1
+ -- ============================================================================
2
+ -- User-erasure helper: redact an email out of invite.created audit payloads
3
+ -- (audit P1)
4
+ -- ============================================================================
5
+ --
6
+ -- `invite.created` domain events embed the invitee's email in the audit
7
+ -- payload (`data->>'email'`). Those rows are authored by the INVITER, so on the
8
+ -- invitee's erasure the row must survive (it records the inviter's action) but
9
+ -- the invitee's email must be scrubbed. This function redacts the `email` key
10
+ -- in place for every matching row, in one statement.
11
+ --
12
+ -- SECURITY DEFINER so `onUserDeleted` can call it through the service-role
13
+ -- client without granting the caller direct UPDATE on audit_events. Scoped to
14
+ -- `type = 'invite.created'` — the only event shape that carries an email.
15
+
16
+ create or replace function selva.redact_audit_event_email(p_email text)
17
+ returns integer
18
+ language plpgsql
19
+ security definer
20
+ set search_path = selva, pg_temp
21
+ as $$
22
+ declare
23
+ affected integer;
24
+ begin
25
+ update selva.audit_events
26
+ set data = jsonb_set(data, '{email}', '"[erased]"'::jsonb)
27
+ where type = 'invite.created'
28
+ and data->>'email' = p_email;
29
+ get diagnostics affected = row_count;
30
+ return affected;
31
+ end;
32
+ $$;
33
+
34
+ comment on function selva.redact_audit_event_email(text) is
35
+ 'Redacts an invitee email out of invite.created audit payloads on user '
36
+ 'erasure (audit P1). Returns the number of rows redacted.';
@@ -0,0 +1,20 @@
1
+ -- ============================================================================
2
+ -- selva.compute_servers.has_api_key — key presence without reading the key (audit 4b)
3
+ -- ============================================================================
4
+ --
5
+ -- Every solve resolves one compute server, but the store's only read was
6
+ -- `getConfig`, which selected `api_key` for EVERY row and decrypted each one on
7
+ -- the way out — N decrypts of secrets the solve path throws away, to use one.
8
+ --
9
+ -- The keys could not simply be dropped from the projection because the admin and
10
+ -- org compute pages render a "key is set" state, which was derived as `!!apiKey`
11
+ -- and so needed the plaintext. This generated column answers that question from
12
+ -- the ciphertext's presence alone, so the default projection can omit `api_key`
13
+ -- entirely: no secret leaves the database to decide whether a badge renders.
14
+ --
15
+ -- STORED (not a view or an expression index) because it is read on the same
16
+ -- projection as every other column and costs a byte per row.
17
+
18
+ alter table selva.compute_servers
19
+ add column if not exists has_api_key boolean
20
+ generated always as (api_key is not null and api_key <> '') stored;
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=conformance.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conformance.test.d.ts","sourceRoot":"","sources":["../../../src/auth/__tests__/conformance.test.ts"],"names":[],"mappings":""}
@@ -1,76 +0,0 @@
1
- import { describe, beforeEach, it } from 'vitest';
2
- import { runAuthProviderConformance, runEmailLinkAuthConformance } from '@selvajs/platform/testing';
3
- import { SupabaseAuthProvider } from '../SupabaseAuthProvider.js';
4
- import { readEnv, resetAllData } from '../../data/__tests__/test-helpers.js';
5
- const envCtx = readEnv();
6
- if (!envCtx) {
7
- describe.skip('SupabaseAuthProvider (skipped: no live stack)', () => {
8
- it('populate packages/providers/supabase/.env.test with Supabase creds to run these tests', () => { });
9
- });
10
- }
11
- else {
12
- const ctx = envCtx; // non-null in this branch
13
- // The auth conformance suite expects `verifyLogin('', adminPassword)` to
14
- // succeed. Supabase requires a non-empty email, so we seed a deterministic
15
- // admin email and have the suite use that via the `adminEmail` option.
16
- const ADMIN_EMAIL = 'admin@conformance.test';
17
- const ADMIN_PASSWORD = 'conformance-admin-password-1234';
18
- async function ensureAdmin() {
19
- const { data: existing } = await ctx.adminClient.auth.admin.listUsers({ perPage: 1000 });
20
- const match = existing.users.find((u) => u.email === ADMIN_EMAIL);
21
- if (match)
22
- return match.id;
23
- const { data, error } = await ctx.adminClient.auth.admin.createUser({
24
- email: ADMIN_EMAIL,
25
- password: ADMIN_PASSWORD,
26
- email_confirm: true
27
- });
28
- if (error)
29
- throw error;
30
- return data.user.id;
31
- }
32
- async function makeProvider() {
33
- const provider = new SupabaseAuthProvider({
34
- supabaseUrl: ctx.url,
35
- anonKey: ctx.anonKey,
36
- serviceRoleKey: ctx.serviceRoleKey
37
- });
38
- // Ensure the admin account exists + has instance_admin.
39
- const id = await ensureAdmin();
40
- const { error: upError } = await ctx.adminClient
41
- .from('user_profiles')
42
- .update({ platform_permissions: ['instance_admin'] })
43
- .eq('user_id', id);
44
- if (upError)
45
- throw upError;
46
- return { provider, adminPassword: ADMIN_PASSWORD };
47
- }
48
- describe('SupabaseAuthProvider', () => {
49
- beforeEach(async () => {
50
- await resetAllData(ctx);
51
- });
52
- runAuthProviderConformance({
53
- name: 'SupabaseAuthProvider',
54
- createProvider: async () => {
55
- const { provider, adminPassword } = await makeProvider();
56
- return { provider, adminPassword, adminEmail: ADMIN_EMAIL };
57
- },
58
- userManagement: true
59
- });
60
- runEmailLinkAuthConformance({
61
- name: 'SupabaseEmailLinkAuth',
62
- createAdapter: async () => {
63
- const { provider } = await makeProvider();
64
- return {
65
- adapter: provider.emailLink,
66
- // `validEmail` isn't used by the suite for live mail delivery;
67
- // it just needs to look like a real address. The malformed
68
- // case is what we actually assert on.
69
- validEmail: 'real-looking@conformance.test',
70
- invalidEmail: 'not-an-email-address'
71
- };
72
- }
73
- });
74
- });
75
- }
76
- //# sourceMappingURL=conformance.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"conformance.test.js","sourceRoot":"","sources":["../../../src/auth/__tests__/conformance.test.ts"],"names":[],"mappings":"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"}
@@ -1,13 +0,0 @@
1
- /**
2
- * CI-runnable (no live stack) tests for §1b hybrid token verification. Stubs
3
- * the internal anon client's `getClaims` (local JWT verify) and `getUser`
4
- * (network GoTrue verify) so we can assert the network-call behavior directly.
5
- *
6
- * Contract:
7
- * - hybrid (default): verify locally via getClaims on every request; hit
8
- * getUser at most once per `revalidateMs` per session; a getUser rejection
9
- * during that recheck (sign-out / disabled) denies immediately.
10
- * - strict: getUser on every request.
11
- */
12
- export {};
13
- //# sourceMappingURL=token-verification.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-verification.test.d.ts","sourceRoot":"","sources":["../../../src/auth/__tests__/token-verification.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -1,135 +0,0 @@
1
- /**
2
- * CI-runnable (no live stack) tests for §1b hybrid token verification. Stubs
3
- * the internal anon client's `getClaims` (local JWT verify) and `getUser`
4
- * (network GoTrue verify) so we can assert the network-call behavior directly.
5
- *
6
- * Contract:
7
- * - hybrid (default): verify locally via getClaims on every request; hit
8
- * getUser at most once per `revalidateMs` per session; a getUser rejection
9
- * during that recheck (sign-out / disabled) denies immediately.
10
- * - strict: getUser on every request.
11
- */
12
- import { describe, it, expect, vi, beforeEach } from 'vitest';
13
- import { SupabaseAuthProvider } from '../SupabaseAuthProvider.js';
14
- const URL = 'https://example.supabase.co';
15
- const ANON = 'anon-key';
16
- const SERVICE = 'service-key';
17
- /** Reach into the provider's private anon client and replace its auth surface. */
18
- function stubAnonAuth(provider) {
19
- const stub = { getClaims: vi.fn(), getUser: vi.fn() };
20
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
- provider.anon.auth.getClaims = stub.getClaims;
22
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
- provider.anon.auth.getUser = stub.getUser;
24
- return stub;
25
- }
26
- function okClaims(overrides = {}) {
27
- return {
28
- data: {
29
- claims: {
30
- sub: 'user-1',
31
- session_id: 'sess-1',
32
- email: 'u@example.com',
33
- exp: Math.floor(Date.now() / 1000) + 3600,
34
- ...overrides
35
- },
36
- header: {},
37
- signature: new Uint8Array()
38
- },
39
- error: null
40
- };
41
- }
42
- function okUser() {
43
- return {
44
- data: { user: { id: 'user-1', email: 'u@example.com', user_metadata: {} } },
45
- error: null
46
- };
47
- }
48
- describe('SupabaseAuthProvider — hybrid token verification', () => {
49
- beforeEach(() => vi.restoreAllMocks());
50
- it('verifies locally via getClaims and rechecks getUser once per window', async () => {
51
- const provider = new SupabaseAuthProvider({
52
- supabaseUrl: URL,
53
- anonKey: ANON,
54
- serviceRoleKey: SERVICE,
55
- revalidateMs: 60_000
56
- });
57
- const stub = stubAnonAuth(provider);
58
- stub.getClaims.mockResolvedValue(okClaims());
59
- stub.getUser.mockResolvedValue(okUser());
60
- // First call: local verify + one recheck (never seen this session).
61
- const a = await provider.verifyToken('tok');
62
- expect(a?.id).toBe('user-1');
63
- expect(stub.getClaims).toHaveBeenCalledTimes(1);
64
- expect(stub.getUser).toHaveBeenCalledTimes(1);
65
- // Subsequent calls within the window: local only, NO more getUser.
66
- await provider.verifyToken('tok');
67
- await provider.verifyToken('tok');
68
- expect(stub.getClaims).toHaveBeenCalledTimes(3);
69
- expect(stub.getUser).toHaveBeenCalledTimes(1); // still just the first
70
- });
71
- it('denies immediately when the periodic getUser recheck rejects (signed out / revoked)', async () => {
72
- const provider = new SupabaseAuthProvider({
73
- supabaseUrl: URL,
74
- anonKey: ANON,
75
- serviceRoleKey: SERVICE
76
- });
77
- const stub = stubAnonAuth(provider);
78
- stub.getClaims.mockResolvedValue(okClaims());
79
- stub.getUser.mockResolvedValue({ data: { user: null }, error: { message: 'signed out' } });
80
- const result = await provider.verifyToken('tok');
81
- expect(result).toBeNull();
82
- });
83
- it('denies when the claims already mark the user disabled (no getUser needed)', async () => {
84
- const provider = new SupabaseAuthProvider({
85
- supabaseUrl: URL,
86
- anonKey: ANON,
87
- serviceRoleKey: SERVICE
88
- });
89
- const stub = stubAnonAuth(provider);
90
- stub.getClaims.mockResolvedValue(okClaims({ user_metadata: { disabled: true } }));
91
- const result = await provider.verifyToken('tok');
92
- expect(result).toBeNull();
93
- expect(stub.getUser).not.toHaveBeenCalled();
94
- });
95
- it('denies when getClaims fails (bad signature / expired)', async () => {
96
- const provider = new SupabaseAuthProvider({
97
- supabaseUrl: URL,
98
- anonKey: ANON,
99
- serviceRoleKey: SERVICE
100
- });
101
- const stub = stubAnonAuth(provider);
102
- stub.getClaims.mockResolvedValue({ data: null, error: { message: 'invalid JWT' } });
103
- expect(await provider.verifyToken('tok')).toBeNull();
104
- expect(stub.getUser).not.toHaveBeenCalled();
105
- });
106
- it('returns null for an empty token without any network call', async () => {
107
- const provider = new SupabaseAuthProvider({
108
- supabaseUrl: URL,
109
- anonKey: ANON,
110
- serviceRoleKey: SERVICE
111
- });
112
- const stub = stubAnonAuth(provider);
113
- expect(await provider.verifyToken('')).toBeNull();
114
- expect(stub.getClaims).not.toHaveBeenCalled();
115
- expect(stub.getUser).not.toHaveBeenCalled();
116
- });
117
- });
118
- describe('SupabaseAuthProvider — strict token verification', () => {
119
- beforeEach(() => vi.restoreAllMocks());
120
- it('calls getUser on every request and never getClaims', async () => {
121
- const provider = new SupabaseAuthProvider({
122
- supabaseUrl: URL,
123
- anonKey: ANON,
124
- serviceRoleKey: SERVICE,
125
- tokenVerification: 'strict'
126
- });
127
- const stub = stubAnonAuth(provider);
128
- stub.getUser.mockResolvedValue(okUser());
129
- await provider.verifyToken('tok');
130
- await provider.verifyToken('tok');
131
- expect(stub.getUser).toHaveBeenCalledTimes(2);
132
- expect(stub.getClaims).not.toHaveBeenCalled();
133
- });
134
- });
135
- //# sourceMappingURL=token-verification.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-verification.test.js","sourceRoot":"","sources":["../../../src/auth/__tests__/token-verification.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=compute-server-conformance.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compute-server-conformance.test.d.ts","sourceRoot":"","sources":["../../../src/data/__tests__/compute-server-conformance.test.ts"],"names":[],"mappings":""}
@@ -1,45 +0,0 @@
1
- import { describe, beforeEach, it } from 'vitest';
2
- import { randomBytes } from 'node:crypto';
3
- import { runComputeServerStoreConformance } from '@selvajs/platform/testing';
4
- import { SupabaseComputeServerStore } from '../SupabaseComputeServerStore.js';
5
- import { readEnv, resetAllData, seedUser } from './test-helpers.js';
6
- const envCtx = readEnv();
7
- // A throwaway at-rest key so the store encrypts apiKeys during the suite. The
8
- // round-trip (encrypt on write, decrypt on read) must be transparent to the
9
- // conformance assertions, which compare plaintext apiKeys.
10
- const TEST_SECRET_KEY = randomBytes(32);
11
- if (!envCtx) {
12
- describe.skip('SupabaseComputeServerStore (skipped: no live stack)', () => {
13
- it('populate packages/providers/supabase/.env.test with Supabase creds to run these tests', () => { });
14
- });
15
- }
16
- else {
17
- describe('SupabaseComputeServerStore', () => {
18
- beforeEach(async () => {
19
- await resetAllData(envCtx);
20
- });
21
- runComputeServerStoreConformance({
22
- name: 'SupabaseComputeServerStore',
23
- createStore: () => new SupabaseComputeServerStore(envCtx.bundle, TEST_SECRET_KEY),
24
- seedOrg: async (orgId) => {
25
- // `compute_servers.owner_org_id` and `compute_server_org_defaults.org_id`
26
- // FK to `orgs(id)`; the suite invents an `orgId` per org-scoped test,
27
- // so we materialize the parent org here. Owner is a throwaway user
28
- // (the org-scoped tests use SYSTEM_CONTEXT for writes anyway).
29
- const { userId } = await seedUser(envCtx, '');
30
- const now = new Date().toISOString();
31
- const { error } = await envCtx.adminClient.from('orgs').insert({
32
- id: orgId,
33
- name: 'Compute Test Org',
34
- slug: `compute-${orgId.slice(0, 8)}`,
35
- owner_id: userId,
36
- created_at: now,
37
- updated_at: now
38
- });
39
- if (error)
40
- throw error;
41
- }
42
- });
43
- });
44
- }
45
- //# sourceMappingURL=compute-server-conformance.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compute-server-conformance.test.js","sourceRoot":"","sources":["../../../src/data/__tests__/compute-server-conformance.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,gCAAgC,EAAE,MAAM,2BAA2B,CAAC;AAC7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC;AAEzB,8EAA8E;AAC9E,4EAA4E;AAC5E,2DAA2D;AAC3D,MAAM,eAAe,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;AAExC,IAAI,CAAC,MAAM,EAAE,CAAC;IACb,QAAQ,CAAC,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;QACzE,EAAE,CAAC,uFAAuF,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;AACJ,CAAC;KAAM,CAAC;IACP,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;QAC3C,UAAU,CAAC,KAAK,IAAI,EAAE;YACrB,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,gCAAgC,CAAC;YAChC,IAAI,EAAE,4BAA4B;YAClC,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,0BAA0B,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC;YACjF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBACxB,0EAA0E;gBAC1E,sEAAsE;gBACtE,mEAAmE;gBACnE,+DAA+D;gBAC/D,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAC9C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;gBACrC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;oBAC9D,EAAE,EAAE,KAAK;oBACT,IAAI,EAAE,kBAAkB;oBACxB,IAAI,EAAE,WAAW,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE;oBACpC,QAAQ,EAAE,MAAM;oBAChB,UAAU,EAAE,GAAG;oBACf,UAAU,EAAE,GAAG;iBACf,CAAC,CAAC;gBACH,IAAI,KAAK;oBAAE,MAAM,KAAK,CAAC;YACxB,CAAC;SACD,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC"}
@@ -1,12 +0,0 @@
1
- /**
2
- * CI-runnable (no live stack) unit tests for at-rest encryption of
3
- * `compute_servers.api_key`. Uses a minimal fake of the Supabase query builder
4
- * that records `insert` payloads and serves canned `select` rows, so we can
5
- * assert the exact bytes that would reach the DB without a real Postgres.
6
- *
7
- * The security property under test: a plaintext apiKey handed to the store is
8
- * NEVER written verbatim — the DB only ever sees an `enc:v1:` envelope — and it
9
- * round-trips back to plaintext on read.
10
- */
11
- export {};
12
- //# sourceMappingURL=compute-server-encryption.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compute-server-encryption.test.d.ts","sourceRoot":"","sources":["../../../src/data/__tests__/compute-server-encryption.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}