@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.
- package/LICENSE +1 -1
- package/README.md +2 -0
- package/dist/auth/SupabaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/SupabaseAuthProvider.js +7 -14
- package/dist/auth/SupabaseAuthProvider.js.map +1 -1
- package/dist/data/SupabaseComputeServerStore.d.ts +14 -3
- package/dist/data/SupabaseComputeServerStore.d.ts.map +1 -1
- package/dist/data/SupabaseComputeServerStore.js +67 -16
- package/dist/data/SupabaseComputeServerStore.js.map +1 -1
- package/dist/data/SupabaseDataProvider.d.ts +36 -8
- package/dist/data/SupabaseDataProvider.d.ts.map +1 -1
- package/dist/data/SupabaseDataProvider.js +119 -15
- package/dist/data/SupabaseDataProvider.js.map +1 -1
- package/dist/data/SupabaseDefinitionStore.d.ts +1 -0
- package/dist/data/SupabaseDefinitionStore.d.ts.map +1 -1
- package/dist/data/SupabaseDefinitionStore.js +41 -4
- package/dist/data/SupabaseDefinitionStore.js.map +1 -1
- package/dist/data/SupabaseEventSink.d.ts +5 -2
- package/dist/data/SupabaseEventSink.d.ts.map +1 -1
- package/dist/data/SupabaseEventSink.js +20 -3
- package/dist/data/SupabaseEventSink.js.map +1 -1
- package/dist/data/SupabaseSolveMetricSink.d.ts +49 -4
- package/dist/data/SupabaseSolveMetricSink.d.ts.map +1 -1
- package/dist/data/SupabaseSolveMetricSink.js +109 -19
- package/dist/data/SupabaseSolveMetricSink.js.map +1 -1
- package/dist/data/index.d.ts +1 -0
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -0
- package/dist/data/index.js.map +1 -1
- package/dist/data/migrationHead.d.ts +14 -0
- package/dist/data/migrationHead.d.ts.map +1 -0
- package/dist/data/migrationHead.js +14 -0
- package/dist/data/migrationHead.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts +5 -3
- package/dist/permissions/SupabasePlatformPermissionStore.d.ts.map +1 -1
- package/dist/permissions/SupabasePlatformPermissionStore.js +17 -30
- package/dist/permissions/SupabasePlatformPermissionStore.js.map +1 -1
- package/dist/storage/SupabaseStorageProvider.d.ts.map +1 -1
- package/dist/storage/SupabaseStorageProvider.js +3 -0
- package/dist/storage/SupabaseStorageProvider.js.map +1 -1
- package/package.json +23 -14
- package/supabase/migrations/20260712120000_user_profiles_disabled_and_gin.sql +95 -0
- package/supabase/migrations/20260712123000_selva_migration_head_fn.sql +38 -0
- package/supabase/migrations/20260713120000_selva_next_version_number.sql +59 -0
- package/supabase/migrations/20260713140000_selva_solve_cache_limit.sql +15 -0
- package/supabase/migrations/20260716120000_definition_status_and_audit_version.sql +36 -0
- package/supabase/migrations/20260716130000_erase_user_audit_email.sql +36 -0
- package/supabase/migrations/20260717120000_compute_server_has_api_key.sql +20 -0
- package/dist/auth/__tests__/conformance.test.d.ts +0 -2
- package/dist/auth/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/auth/__tests__/conformance.test.js +0 -76
- package/dist/auth/__tests__/conformance.test.js.map +0 -1
- package/dist/auth/__tests__/token-verification.test.d.ts +0 -13
- package/dist/auth/__tests__/token-verification.test.d.ts.map +0 -1
- package/dist/auth/__tests__/token-verification.test.js +0 -135
- package/dist/auth/__tests__/token-verification.test.js.map +0 -1
- package/dist/data/__tests__/compute-server-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/compute-server-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/compute-server-conformance.test.js +0 -45
- package/dist/data/__tests__/compute-server-conformance.test.js.map +0 -1
- package/dist/data/__tests__/compute-server-encryption.test.d.ts +0 -12
- package/dist/data/__tests__/compute-server-encryption.test.d.ts.map +0 -1
- package/dist/data/__tests__/compute-server-encryption.test.js +0 -143
- package/dist/data/__tests__/compute-server-encryption.test.js.map +0 -1
- package/dist/data/__tests__/definition-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/definition-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/definition-conformance.test.js +0 -85
- package/dist/data/__tests__/definition-conformance.test.js.map +0 -1
- package/dist/data/__tests__/event-sink-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/event-sink-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/event-sink-conformance.test.js +0 -27
- package/dist/data/__tests__/event-sink-conformance.test.js.map +0 -1
- package/dist/data/__tests__/invite-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/invite-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/invite-conformance.test.js +0 -45
- package/dist/data/__tests__/invite-conformance.test.js.map +0 -1
- package/dist/data/__tests__/org-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/org-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/org-conformance.test.js +0 -29
- package/dist/data/__tests__/org-conformance.test.js.map +0 -1
- package/dist/data/__tests__/project-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/project-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/project-conformance.test.js +0 -57
- package/dist/data/__tests__/project-conformance.test.js.map +0 -1
- package/dist/data/__tests__/share-link-concurrency.test.d.ts +0 -22
- package/dist/data/__tests__/share-link-concurrency.test.d.ts.map +0 -1
- package/dist/data/__tests__/share-link-concurrency.test.js +0 -115
- package/dist/data/__tests__/share-link-concurrency.test.js.map +0 -1
- package/dist/data/__tests__/share-link-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/share-link-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/share-link-conformance.test.js +0 -88
- package/dist/data/__tests__/share-link-conformance.test.js.map +0 -1
- package/dist/data/__tests__/solve-metric-sink-conformance.test.d.ts +0 -2
- package/dist/data/__tests__/solve-metric-sink-conformance.test.d.ts.map +0 -1
- package/dist/data/__tests__/solve-metric-sink-conformance.test.js +0 -52
- package/dist/data/__tests__/solve-metric-sink-conformance.test.js.map +0 -1
- package/dist/data/__tests__/test-helpers.d.ts +0 -52
- package/dist/data/__tests__/test-helpers.d.ts.map +0 -1
- package/dist/data/__tests__/test-helpers.js +0 -177
- package/dist/data/__tests__/test-helpers.js.map +0 -1
- package/dist/permissions/__tests__/conformance.test.d.ts +0 -2
- package/dist/permissions/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/permissions/__tests__/conformance.test.js +0 -27
- package/dist/permissions/__tests__/conformance.test.js.map +0 -1
- package/dist/storage/__tests__/conformance.test.d.ts +0 -2
- package/dist/storage/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/storage/__tests__/conformance.test.js +0 -149
- package/dist/storage/__tests__/conformance.test.js.map +0 -1
- package/dist/userProfile/__tests__/conformance.test.d.ts +0 -2
- package/dist/userProfile/__tests__/conformance.test.d.ts.map +0 -1
- package/dist/userProfile/__tests__/conformance.test.js +0 -38
- package/dist/userProfile/__tests__/conformance.test.js.map +0 -1
- package/src/auth/SupabaseAuthProvider.ts +0 -600
- package/src/auth/__tests__/conformance.test.ts +0 -78
- package/src/auth/__tests__/token-verification.test.ts +0 -158
- package/src/auth/index.ts +0 -2
- package/src/cli/sync-migrations.ts +0 -133
- package/src/data/SupabaseAuditQuery.ts +0 -103
- package/src/data/SupabaseComputeServerStore.ts +0 -382
- package/src/data/SupabaseDataProvider.ts +0 -153
- package/src/data/SupabaseDefinitionStore.ts +0 -527
- package/src/data/SupabaseEventSink.ts +0 -30
- package/src/data/SupabaseInviteStore.ts +0 -171
- package/src/data/SupabaseOrgStore.ts +0 -508
- package/src/data/SupabaseProjectStore.ts +0 -431
- package/src/data/SupabaseShareLinkStore.ts +0 -176
- package/src/data/SupabaseSolveMetricSink.ts +0 -38
- package/src/data/__tests__/compute-server-conformance.test.ts +0 -46
- package/src/data/__tests__/compute-server-encryption.test.ts +0 -173
- package/src/data/__tests__/definition-conformance.test.ts +0 -89
- package/src/data/__tests__/event-sink-conformance.test.ts +0 -32
- package/src/data/__tests__/invite-conformance.test.ts +0 -46
- package/src/data/__tests__/org-conformance.test.ts +0 -30
- package/src/data/__tests__/project-conformance.test.ts +0 -57
- package/src/data/__tests__/share-link-concurrency.test.ts +0 -124
- package/src/data/__tests__/share-link-conformance.test.ts +0 -87
- package/src/data/__tests__/solve-metric-sink-conformance.test.ts +0 -59
- package/src/data/__tests__/test-helpers.ts +0 -188
- package/src/data/client.ts +0 -173
- package/src/data/errors.ts +0 -45
- package/src/data/index.ts +0 -29
- package/src/data/pagination.ts +0 -68
- package/src/data/query.ts +0 -22
- package/src/data/rowStamp.ts +0 -48
- package/src/index.ts +0 -42
- package/src/permissions/SupabasePlatformPermissionStore.ts +0 -161
- package/src/permissions/__tests__/conformance.test.ts +0 -28
- package/src/permissions/index.ts +0 -1
- package/src/storage/SupabaseStorageProvider.ts +0 -182
- package/src/storage/__tests__/conformance.test.ts +0 -152
- package/src/storage/index.ts +0 -2
- package/src/userProfile/SupabaseUserProfileProvider.ts +0 -177
- package/src/userProfile/__tests__/conformance.test.ts +0 -39
- 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 +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 +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"}
|