Initial working state: CL3.5 complete (Plesk sync + suspend/unsuspend)
This commit is contained in:
375
supabase/migrations/CL2_rls_policies.sql
Normal file
375
supabase/migrations/CL2_rls_policies.sql
Normal file
@@ -0,0 +1,375 @@
|
||||
-- CL2: Multi-tenant RLS hardening + onboarding-safe policies
|
||||
|
||||
create extension if not exists pgcrypto;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Helper functions
|
||||
-- ---------------------------------------------------------------------------
|
||||
create or replace function public.is_agency_member(target_agency_id uuid)
|
||||
returns boolean
|
||||
language sql
|
||||
stable
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
select exists (
|
||||
select 1
|
||||
from public.agency_members am
|
||||
where am.agency_id = target_agency_id
|
||||
and am.user_id = auth.uid()
|
||||
);
|
||||
$$;
|
||||
|
||||
create or replace function public.is_agency_admin(target_agency_id uuid)
|
||||
returns boolean
|
||||
language sql
|
||||
stable
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
select exists (
|
||||
select 1
|
||||
from public.agency_members am
|
||||
where am.agency_id = target_agency_id
|
||||
and am.user_id = auth.uid()
|
||||
and am.role in ('owner', 'admin')
|
||||
);
|
||||
$$;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- RLS enablement
|
||||
-- ---------------------------------------------------------------------------
|
||||
alter table public.agencies enable row level security;
|
||||
alter table public.agency_members enable row level security;
|
||||
alter table public.clients enable row level security;
|
||||
alter table public.plesk_instances enable row level security;
|
||||
alter table public.plesk_subscriptions enable row level security;
|
||||
alter table public.plesk_domains enable row level security;
|
||||
alter table public.actions_log enable row level security;
|
||||
alter table public.billing_accounts enable row level security;
|
||||
alter table public.entitlements enable row level security;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Drop legacy policies (if present)
|
||||
-- ---------------------------------------------------------------------------
|
||||
drop policy if exists agencies_select on public.agencies;
|
||||
drop policy if exists agencies_update on public.agencies;
|
||||
drop policy if exists agency_members_select on public.agency_members;
|
||||
drop policy if exists agency_members_manage on public.agency_members;
|
||||
drop policy if exists clients_member_access on public.clients;
|
||||
drop policy if exists plesk_instances_member_access on public.plesk_instances;
|
||||
drop policy if exists plesk_subscriptions_member_access on public.plesk_subscriptions;
|
||||
drop policy if exists plesk_domains_member_access on public.plesk_domains;
|
||||
drop policy if exists actions_log_member_access on public.actions_log;
|
||||
drop policy if exists billing_accounts_member_access on public.billing_accounts;
|
||||
drop policy if exists entitlements_member_access on public.entitlements;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- agencies
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy agencies_select_member
|
||||
on public.agencies
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(id));
|
||||
|
||||
create policy agencies_insert_authenticated
|
||||
on public.agencies
|
||||
for insert
|
||||
to authenticated
|
||||
with check (true);
|
||||
|
||||
create policy agencies_update_admin
|
||||
on public.agencies
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(id))
|
||||
with check (public.is_agency_admin(id));
|
||||
|
||||
create policy agencies_delete_admin
|
||||
on public.agencies
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- agency_members
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy agency_members_select_self
|
||||
on public.agency_members
|
||||
for select
|
||||
to authenticated
|
||||
using (user_id = auth.uid());
|
||||
|
||||
-- Onboarding: user can create their own owner membership for their first agency.
|
||||
create policy agency_members_insert_self_onboarding
|
||||
on public.agency_members
|
||||
for insert
|
||||
to authenticated
|
||||
with check (
|
||||
user_id = auth.uid()
|
||||
and role = 'owner'
|
||||
and not exists (
|
||||
select 1
|
||||
from public.agency_members am
|
||||
where am.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- Admins can add members to their agency.
|
||||
create policy agency_members_insert_admin
|
||||
on public.agency_members
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy agency_members_update_admin
|
||||
on public.agency_members
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy agency_members_delete_admin_or_self
|
||||
on public.agency_members
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id) or user_id = auth.uid());
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- clients (member writable by design)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy clients_select_member
|
||||
on public.clients
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy clients_insert_member
|
||||
on public.clients
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_member(agency_id));
|
||||
|
||||
create policy clients_update_member
|
||||
on public.clients
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id))
|
||||
with check (public.is_agency_member(agency_id));
|
||||
|
||||
create policy clients_delete_member
|
||||
on public.clients
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- plesk_instances (admin-writable)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy plesk_instances_select_member
|
||||
on public.plesk_instances
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy plesk_instances_insert_admin
|
||||
on public.plesk_instances
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy plesk_instances_update_admin
|
||||
on public.plesk_instances
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy plesk_instances_delete_admin
|
||||
on public.plesk_instances
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- plesk_subscriptions (admin-writable synced data)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy plesk_subscriptions_select_member
|
||||
on public.plesk_subscriptions
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy plesk_subscriptions_insert_admin
|
||||
on public.plesk_subscriptions
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy plesk_subscriptions_update_admin
|
||||
on public.plesk_subscriptions
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy plesk_subscriptions_delete_admin
|
||||
on public.plesk_subscriptions
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- plesk_domains (admin-writable synced data)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy plesk_domains_select_member
|
||||
on public.plesk_domains
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy plesk_domains_insert_admin
|
||||
on public.plesk_domains
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy plesk_domains_update_admin
|
||||
on public.plesk_domains
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy plesk_domains_delete_admin
|
||||
on public.plesk_domains
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- actions_log
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy actions_log_select_member
|
||||
on public.actions_log
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy actions_log_insert_admin
|
||||
on public.actions_log
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy actions_log_update_admin
|
||||
on public.actions_log
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- billing_accounts (admin-writable)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy billing_accounts_select_member
|
||||
on public.billing_accounts
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy billing_accounts_insert_admin
|
||||
on public.billing_accounts
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy billing_accounts_update_admin
|
||||
on public.billing_accounts
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy billing_accounts_delete_admin
|
||||
on public.billing_accounts
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- entitlements (admin-writable)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create policy entitlements_select_member
|
||||
on public.entitlements
|
||||
for select
|
||||
to authenticated
|
||||
using (public.is_agency_member(agency_id));
|
||||
|
||||
create policy entitlements_insert_admin
|
||||
on public.entitlements
|
||||
for insert
|
||||
to authenticated
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy entitlements_update_admin
|
||||
on public.entitlements
|
||||
for update
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id))
|
||||
with check (public.is_agency_admin(agency_id));
|
||||
|
||||
create policy entitlements_delete_admin
|
||||
on public.entitlements
|
||||
for delete
|
||||
to authenticated
|
||||
using (public.is_agency_admin(agency_id));
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Indexes (if missing)
|
||||
-- ---------------------------------------------------------------------------
|
||||
create index if not exists idx_agency_members_agency_id on public.agency_members (agency_id);
|
||||
create index if not exists idx_agency_members_user_id on public.agency_members (user_id);
|
||||
|
||||
create index if not exists idx_clients_agency_id on public.clients (agency_id);
|
||||
|
||||
create index if not exists idx_plesk_instances_agency_id on public.plesk_instances (agency_id);
|
||||
|
||||
create index if not exists idx_plesk_subscriptions_agency_id on public.plesk_subscriptions (agency_id);
|
||||
create index if not exists idx_plesk_subscriptions_instance_id on public.plesk_subscriptions (plesk_instance_id);
|
||||
create index if not exists idx_plesk_subscriptions_client_id on public.plesk_subscriptions (client_id);
|
||||
|
||||
create index if not exists idx_plesk_domains_agency_id on public.plesk_domains (agency_id);
|
||||
create index if not exists idx_plesk_domains_instance_id on public.plesk_domains (plesk_instance_id);
|
||||
create index if not exists idx_plesk_domains_subscription_id on public.plesk_domains (subscription_id);
|
||||
|
||||
create index if not exists idx_actions_log_agency_id on public.actions_log (agency_id);
|
||||
create index if not exists idx_actions_log_target_id on public.actions_log (target_id);
|
||||
|
||||
create index if not exists idx_billing_accounts_agency_id on public.billing_accounts (agency_id);
|
||||
|
||||
create index if not exists idx_entitlements_agency_id on public.entitlements (agency_id);
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- updated_at trigger safety for expected tables
|
||||
-- ---------------------------------------------------------------------------
|
||||
create or replace function public.set_updated_at()
|
||||
returns trigger
|
||||
language plpgsql
|
||||
as $$
|
||||
begin
|
||||
new.updated_at = now();
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
drop trigger if exists trg_plesk_subscriptions_updated_at on public.plesk_subscriptions;
|
||||
create trigger trg_plesk_subscriptions_updated_at
|
||||
before update on public.plesk_subscriptions
|
||||
for each row execute function public.set_updated_at();
|
||||
|
||||
drop trigger if exists trg_billing_accounts_updated_at on public.billing_accounts;
|
||||
create trigger trg_billing_accounts_updated_at
|
||||
before update on public.billing_accounts
|
||||
for each row execute function public.set_updated_at();
|
||||
49
supabase/migrations/CL35_auto_sync_job_locks.sql
Normal file
49
supabase/migrations/CL35_auto_sync_job_locks.sql
Normal file
@@ -0,0 +1,49 @@
|
||||
-- CL3.5: background auto-sync lock primitives
|
||||
|
||||
create table if not exists public.job_locks (
|
||||
job_name text primary key,
|
||||
locked_at timestamptz not null default now(),
|
||||
locked_by text,
|
||||
expires_at timestamptz not null
|
||||
);
|
||||
|
||||
create or replace function public.acquire_job_lock(
|
||||
p_job_name text,
|
||||
p_locked_by text,
|
||||
p_ttl_seconds integer default 240
|
||||
)
|
||||
returns boolean
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_acquired boolean := false;
|
||||
begin
|
||||
insert into public.job_locks (job_name, locked_by, locked_at, expires_at)
|
||||
values (
|
||||
p_job_name,
|
||||
p_locked_by,
|
||||
now(),
|
||||
now() + make_interval(secs => p_ttl_seconds)
|
||||
)
|
||||
on conflict (job_name)
|
||||
do update
|
||||
set locked_by = excluded.locked_by,
|
||||
locked_at = excluded.locked_at,
|
||||
expires_at = excluded.expires_at
|
||||
where public.job_locks.expires_at < now();
|
||||
|
||||
get diagnostics v_acquired = row_count;
|
||||
return v_acquired;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.release_job_lock(p_job_name text)
|
||||
returns void
|
||||
language sql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
delete from public.job_locks where job_name = p_job_name;
|
||||
$$;
|
||||
@@ -0,0 +1,37 @@
|
||||
-- CL3.6a: Instance health + domains visibility
|
||||
|
||||
alter table public.plesk_instances
|
||||
add column if not exists last_sync_at timestamptz,
|
||||
add column if not exists last_sync_status text,
|
||||
add column if not exists last_sync_error text,
|
||||
add column if not exists last_sync_subscriptions integer not null default 0,
|
||||
add column if not exists last_sync_domains integer not null default 0;
|
||||
|
||||
update public.plesk_instances
|
||||
set
|
||||
last_sync_subscriptions = coalesce(last_sync_subscriptions, 0),
|
||||
last_sync_domains = coalesce(last_sync_domains, 0);
|
||||
|
||||
alter table public.plesk_instances
|
||||
alter column last_sync_subscriptions set default 0,
|
||||
alter column last_sync_domains set default 0,
|
||||
alter column last_sync_subscriptions set not null,
|
||||
alter column last_sync_domains set not null;
|
||||
|
||||
alter table public.plesk_instances
|
||||
drop constraint if exists plesk_instances_status_check;
|
||||
|
||||
alter table public.plesk_instances
|
||||
add constraint plesk_instances_status_check
|
||||
check (status in ('connected', 'error', 'disabled', 'syncing'));
|
||||
|
||||
alter table public.plesk_domains
|
||||
add column if not exists hosting_type text,
|
||||
add column if not exists source_created_at timestamptz,
|
||||
add column if not exists aliases_count integer,
|
||||
add column if not exists updated_at timestamptz not null default now();
|
||||
|
||||
drop trigger if exists trg_plesk_domains_updated_at on public.plesk_domains;
|
||||
create trigger trg_plesk_domains_updated_at
|
||||
before update on public.plesk_domains
|
||||
for each row execute function public.set_updated_at();
|
||||
7
supabase/migrations/CL3_patch_domains_nullable.sql
Normal file
7
supabase/migrations/CL3_patch_domains_nullable.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
-- CL3 Patch: allow domain sync without strict subscription mapping
|
||||
|
||||
alter table public.plesk_domains
|
||||
alter column subscription_id drop not null;
|
||||
|
||||
alter table public.plesk_domains
|
||||
add column if not exists external_subscription_id text;
|
||||
106
supabase/migrations/CL3_plesk_connection_sync.sql
Normal file
106
supabase/migrations/CL3_plesk_connection_sync.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
-- CL3: Plesk connection + sync MVP hardening
|
||||
|
||||
-- 1) plesk_instances credential/storage hardening
|
||||
alter table public.plesk_instances
|
||||
add column if not exists encrypted_secret text,
|
||||
add column if not exists secret_kid text,
|
||||
add column if not exists error_message text;
|
||||
|
||||
do $$
|
||||
begin
|
||||
if exists (
|
||||
select 1
|
||||
from information_schema.columns
|
||||
where table_schema = 'public'
|
||||
and table_name = 'plesk_instances'
|
||||
and column_name = 'auth_secret_encrypted'
|
||||
) then
|
||||
update public.plesk_instances
|
||||
set encrypted_secret = coalesce(encrypted_secret, auth_secret_encrypted)
|
||||
where encrypted_secret is null;
|
||||
|
||||
alter table public.plesk_instances
|
||||
drop column auth_secret_encrypted;
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
update public.plesk_instances
|
||||
set status = 'connected'
|
||||
where status = 'active';
|
||||
|
||||
alter table public.plesk_instances
|
||||
alter column encrypted_secret set not null,
|
||||
alter column status set default 'error';
|
||||
|
||||
alter table public.plesk_instances
|
||||
drop constraint if exists plesk_instances_auth_type_check;
|
||||
|
||||
alter table public.plesk_instances
|
||||
add constraint plesk_instances_auth_type_check
|
||||
check (auth_type in ('api_key', 'basic'));
|
||||
|
||||
alter table public.plesk_instances
|
||||
drop constraint if exists plesk_instances_status_check;
|
||||
|
||||
alter table public.plesk_instances
|
||||
add constraint plesk_instances_status_check
|
||||
check (status in ('connected', 'error', 'disabled'));
|
||||
|
||||
-- 2) FK guarantees
|
||||
do $$
|
||||
begin
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'plesk_instances_agency_id_fkey'
|
||||
) then
|
||||
alter table public.plesk_instances
|
||||
add constraint plesk_instances_agency_id_fkey
|
||||
foreign key (agency_id) references public.agencies(id) on delete cascade;
|
||||
end if;
|
||||
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'plesk_subscriptions_plesk_instance_id_fkey'
|
||||
) then
|
||||
alter table public.plesk_subscriptions
|
||||
add constraint plesk_subscriptions_plesk_instance_id_fkey
|
||||
foreign key (plesk_instance_id) references public.plesk_instances(id) on delete cascade;
|
||||
end if;
|
||||
|
||||
if not exists (
|
||||
select 1 from pg_constraint
|
||||
where conname = 'plesk_domains_subscription_id_fkey'
|
||||
) then
|
||||
alter table public.plesk_domains
|
||||
add constraint plesk_domains_subscription_id_fkey
|
||||
foreign key (subscription_id) references public.plesk_subscriptions(id) on delete cascade;
|
||||
end if;
|
||||
end $$;
|
||||
|
||||
-- 3) Uniques + indexes
|
||||
create unique index if not exists uq_plesk_instances_agency_base_url
|
||||
on public.plesk_instances (agency_id, base_url);
|
||||
|
||||
create unique index if not exists uq_plesk_subscriptions_agency_external
|
||||
on public.plesk_subscriptions (agency_id, plesk_subscription_id);
|
||||
|
||||
create unique index if not exists uq_plesk_domains_agency_external
|
||||
on public.plesk_domains (agency_id, plesk_domain_id);
|
||||
|
||||
create index if not exists idx_plesk_instances_agency_id
|
||||
on public.plesk_instances (agency_id);
|
||||
|
||||
create index if not exists idx_plesk_subscriptions_agency_id
|
||||
on public.plesk_subscriptions (agency_id);
|
||||
|
||||
create index if not exists idx_plesk_subscriptions_instance_id
|
||||
on public.plesk_subscriptions (plesk_instance_id);
|
||||
|
||||
create index if not exists idx_plesk_domains_agency_id
|
||||
on public.plesk_domains (agency_id);
|
||||
|
||||
create index if not exists idx_plesk_domains_instance_id
|
||||
on public.plesk_domains (plesk_instance_id);
|
||||
|
||||
create index if not exists idx_plesk_domains_subscription_id
|
||||
on public.plesk_domains (subscription_id);
|
||||
Reference in New Issue
Block a user