Sync deployment and project page fixes

This commit is contained in:
willchen96 2026-05-13 02:32:26 +08:00
parent 91d0c2a089
commit f39f175273
13 changed files with 1444 additions and 1315 deletions

View file

@ -25,18 +25,6 @@ create table if not exists public.user_profiles (
create index if not exists idx_user_profiles_user
on public.user_profiles(user_id);
alter table public.user_profiles enable row level security;
drop policy if exists "Users can view their own profile" on public.user_profiles;
create policy "Users can view their own profile"
on public.user_profiles for select
using (auth.uid() = user_id);
drop policy if exists "Users can update their own profile" on public.user_profiles;
create policy "Users can update their own profile"
on public.user_profiles for update
using (auth.uid() = user_id);
create or replace function public.handle_new_user()
returns trigger
language plpgsql
@ -74,8 +62,6 @@ create table if not exists public.user_api_keys (
create index if not exists idx_user_api_keys_user
on public.user_api_keys(user_id);
alter table public.user_api_keys enable row level security;
-- ---------------------------------------------------------------------------
-- Projects and documents
-- ---------------------------------------------------------------------------
@ -354,705 +340,13 @@ create table if not exists public.tabular_review_chat_messages (
create index if not exists tabular_review_chat_messages_chat_idx
on public.tabular_review_chat_messages(chat_id, created_at);
-- ---------------------------------------------------------------------------
-- Row-level security
-- ---------------------------------------------------------------------------
create or replace function public.current_user_id_text()
returns text
language sql
stable
set search_path = public, auth
as $$
select auth.uid()::text;
$$;
create or replace function public.current_user_email()
returns text
language sql
stable
set search_path = public, auth
as $$
select lower(coalesce(auth.jwt() ->> 'email', ''));
$$;
create or replace function public.email_is_shared(shared_with jsonb)
returns boolean
language sql
stable
set search_path = public
as $$
select public.current_user_email() <> ''
and exists (
select 1
from jsonb_array_elements_text(coalesce(shared_with, '[]'::jsonb)) as emails(email)
where lower(emails.email) = public.current_user_email()
);
$$;
create or replace function public.project_is_accessible(target_project_id uuid)
returns boolean
language sql
stable
security definer
set search_path = public, auth
as $$
select exists (
select 1
from public.projects p
where p.id = target_project_id
and (
p.user_id = public.current_user_id_text()
or public.email_is_shared(p.shared_with)
)
);
$$;
create or replace function public.review_is_accessible(target_review_id uuid)
returns boolean
language sql
stable
security definer
set search_path = public, auth
as $$
select exists (
select 1
from public.tabular_reviews r
where r.id = target_review_id
and (
r.user_id = public.current_user_id_text()
or public.email_is_shared(r.shared_with)
or (
r.project_id is not null
and public.project_is_accessible(r.project_id)
)
)
);
$$;
create or replace function public.workflow_can_view(target_workflow_id uuid)
returns boolean
language sql
stable
security definer
set search_path = public, auth
as $$
select exists (
select 1
from public.workflows w
where w.id = target_workflow_id
and (
w.is_system
or w.user_id = public.current_user_id_text()
or exists (
select 1
from public.workflow_shares s
where s.workflow_id = w.id
and s.shared_with_email = public.current_user_email()
)
)
);
$$;
create or replace function public.workflow_can_edit(target_workflow_id uuid)
returns boolean
language sql
stable
security definer
set search_path = public, auth
as $$
select exists (
select 1
from public.workflows w
where w.id = target_workflow_id
and (
w.user_id = public.current_user_id_text()
or exists (
select 1
from public.workflow_shares s
where s.workflow_id = w.id
and s.shared_with_email = public.current_user_email()
and s.allow_edit
)
)
);
$$;
alter table public.user_profiles enable row level security;
alter table public.user_api_keys enable row level security;
alter table public.projects enable row level security;
alter table public.project_subfolders enable row level security;
alter table public.documents enable row level security;
alter table public.document_versions enable row level security;
alter table public.document_edits enable row level security;
alter table public.workflows enable row level security;
alter table public.hidden_workflows enable row level security;
alter table public.workflow_shares enable row level security;
alter table public.chats enable row level security;
alter table public.chat_messages enable row level security;
alter table public.tabular_reviews enable row level security;
alter table public.tabular_cells enable row level security;
alter table public.tabular_review_chats enable row level security;
alter table public.tabular_review_chat_messages enable row level security;
drop policy if exists "Users can insert their own profile" on public.user_profiles;
create policy "Users can insert their own profile"
on public.user_profiles for insert
with check (auth.uid() = user_id);
drop policy if exists "Users can view their own profile" on public.user_profiles;
create policy "Users can view their own profile"
on public.user_profiles for select
using (auth.uid() = user_id);
drop policy if exists "Users can update their own profile" on public.user_profiles;
create policy "Users can update their own profile"
on public.user_profiles for update
using (auth.uid() = user_id)
with check (auth.uid() = user_id);
-- user_api_keys is intentionally service-role only. The browser can only see
-- key status through backend routes, never encrypted key material.
drop policy if exists "Users can view accessible projects" on public.projects;
create policy "Users can view accessible projects"
on public.projects for select
using (
user_id = public.current_user_id_text()
or public.email_is_shared(shared_with)
);
drop policy if exists "Users can insert their own projects" on public.projects;
create policy "Users can insert their own projects"
on public.projects for insert
with check (user_id = public.current_user_id_text());
drop policy if exists "Owners can update projects" on public.projects;
create policy "Owners can update projects"
on public.projects for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Owners can delete projects" on public.projects;
create policy "Owners can delete projects"
on public.projects for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible project folders" on public.project_subfolders;
create policy "Users can view accessible project folders"
on public.project_subfolders for select
using (
user_id = public.current_user_id_text()
or public.project_is_accessible(project_id)
);
drop policy if exists "Users can insert their own project folders" on public.project_subfolders;
create policy "Users can insert their own project folders"
on public.project_subfolders for insert
with check (
user_id = public.current_user_id_text()
and public.project_is_accessible(project_id)
);
drop policy if exists "Owners can update project folders" on public.project_subfolders;
create policy "Owners can update project folders"
on public.project_subfolders for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Owners can delete project folders" on public.project_subfolders;
create policy "Owners can delete project folders"
on public.project_subfolders for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible documents" on public.documents;
create policy "Users can view accessible documents"
on public.documents for select
using (
user_id = public.current_user_id_text()
or (
project_id is not null
and public.project_is_accessible(project_id)
)
);
drop policy if exists "Users can insert their own documents" on public.documents;
create policy "Users can insert their own documents"
on public.documents for insert
with check (
user_id = public.current_user_id_text()
and (
project_id is null
or public.project_is_accessible(project_id)
)
);
drop policy if exists "Owners can update documents" on public.documents;
create policy "Owners can update documents"
on public.documents for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Owners can delete documents" on public.documents;
create policy "Owners can delete documents"
on public.documents for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible document versions" on public.document_versions;
create policy "Users can view accessible document versions"
on public.document_versions for select
using (
exists (
select 1
from public.documents d
where d.id = document_id
and (
d.user_id = public.current_user_id_text()
or (
d.project_id is not null
and public.project_is_accessible(d.project_id)
)
)
)
);
drop policy if exists "Document owners can insert versions" on public.document_versions;
create policy "Document owners can insert versions"
on public.document_versions for insert
with check (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
);
drop policy if exists "Document owners can update versions" on public.document_versions;
create policy "Document owners can update versions"
on public.document_versions for update
using (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
)
with check (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
);
drop policy if exists "Document owners can delete versions" on public.document_versions;
create policy "Document owners can delete versions"
on public.document_versions for delete
using (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
);
drop policy if exists "Users can view accessible document edits" on public.document_edits;
create policy "Users can view accessible document edits"
on public.document_edits for select
using (
exists (
select 1
from public.documents d
where d.id = document_id
and (
d.user_id = public.current_user_id_text()
or (
d.project_id is not null
and public.project_is_accessible(d.project_id)
)
)
)
);
drop policy if exists "Document owners can insert edits" on public.document_edits;
create policy "Document owners can insert edits"
on public.document_edits for insert
with check (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
);
drop policy if exists "Document owners can update edits" on public.document_edits;
create policy "Document owners can update edits"
on public.document_edits for update
using (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
)
with check (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
);
drop policy if exists "Document owners can delete edits" on public.document_edits;
create policy "Document owners can delete edits"
on public.document_edits for delete
using (
exists (
select 1
from public.documents d
where d.id = document_id
and d.user_id = public.current_user_id_text()
)
);
drop policy if exists "Users can view accessible workflows" on public.workflows;
create policy "Users can view accessible workflows"
on public.workflows for select
using (public.workflow_can_view(id));
drop policy if exists "Users can insert their own workflows" on public.workflows;
create policy "Users can insert their own workflows"
on public.workflows for insert
with check (user_id = public.current_user_id_text());
drop policy if exists "Workflow owners can update workflows" on public.workflows;
create policy "Workflow owners can update workflows"
on public.workflows for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Workflow owners can delete workflows" on public.workflows;
create policy "Workflow owners can delete workflows"
on public.workflows for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can manage their hidden workflows" on public.hidden_workflows;
create policy "Users can manage their hidden workflows"
on public.hidden_workflows for all
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Users can view relevant workflow shares" on public.workflow_shares;
create policy "Users can view relevant workflow shares"
on public.workflow_shares for select
using (
shared_by_user_id = public.current_user_id_text()
or shared_with_email = public.current_user_email()
);
drop policy if exists "Workflow owners can insert shares" on public.workflow_shares;
create policy "Workflow owners can insert shares"
on public.workflow_shares for insert
with check (
shared_by_user_id = public.current_user_id_text()
and exists (
select 1
from public.workflows w
where w.id = workflow_id
and w.user_id = public.current_user_id_text()
)
);
drop policy if exists "Workflow owners can update shares" on public.workflow_shares;
create policy "Workflow owners can update shares"
on public.workflow_shares for update
using (shared_by_user_id = public.current_user_id_text())
with check (shared_by_user_id = public.current_user_id_text());
drop policy if exists "Workflow owners can delete shares" on public.workflow_shares;
create policy "Workflow owners can delete shares"
on public.workflow_shares for delete
using (shared_by_user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible chats" on public.chats;
create policy "Users can view accessible chats"
on public.chats for select
using (
user_id = public.current_user_id_text()
or (
project_id is not null
and public.project_is_accessible(project_id)
)
);
drop policy if exists "Users can insert their own chats" on public.chats;
create policy "Users can insert their own chats"
on public.chats for insert
with check (
user_id = public.current_user_id_text()
and (
project_id is null
or public.project_is_accessible(project_id)
)
);
drop policy if exists "Chat owners can update chats" on public.chats;
create policy "Chat owners can update chats"
on public.chats for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Chat owners can delete chats" on public.chats;
create policy "Chat owners can delete chats"
on public.chats for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible chat messages" on public.chat_messages;
create policy "Users can view accessible chat messages"
on public.chat_messages for select
using (
exists (
select 1
from public.chats c
where c.id = chat_id
and (
c.user_id = public.current_user_id_text()
or (
c.project_id is not null
and public.project_is_accessible(c.project_id)
)
)
)
);
drop policy if exists "Chat owners can insert messages" on public.chat_messages;
create policy "Chat owners can insert messages"
on public.chat_messages for insert
with check (
exists (
select 1
from public.chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
);
drop policy if exists "Chat owners can update messages" on public.chat_messages;
create policy "Chat owners can update messages"
on public.chat_messages for update
using (
exists (
select 1
from public.chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
)
with check (
exists (
select 1
from public.chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
);
drop policy if exists "Chat owners can delete messages" on public.chat_messages;
create policy "Chat owners can delete messages"
on public.chat_messages for delete
using (
exists (
select 1
from public.chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
);
drop policy if exists "Users can view accessible tabular reviews" on public.tabular_reviews;
create policy "Users can view accessible tabular reviews"
on public.tabular_reviews for select
using (
user_id = public.current_user_id_text()
or public.email_is_shared(shared_with)
or (
project_id is not null
and public.project_is_accessible(project_id)
)
);
drop policy if exists "Users can insert their own tabular reviews" on public.tabular_reviews;
create policy "Users can insert their own tabular reviews"
on public.tabular_reviews for insert
with check (
user_id = public.current_user_id_text()
and (
project_id is null
or public.project_is_accessible(project_id)
)
);
drop policy if exists "Review owners can update tabular reviews" on public.tabular_reviews;
create policy "Review owners can update tabular reviews"
on public.tabular_reviews for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Review owners can delete tabular reviews" on public.tabular_reviews;
create policy "Review owners can delete tabular reviews"
on public.tabular_reviews for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible tabular cells" on public.tabular_cells;
create policy "Users can view accessible tabular cells"
on public.tabular_cells for select
using (public.review_is_accessible(review_id));
drop policy if exists "Review owners can insert tabular cells" on public.tabular_cells;
create policy "Review owners can insert tabular cells"
on public.tabular_cells for insert
with check (
exists (
select 1
from public.tabular_reviews r
where r.id = review_id
and r.user_id = public.current_user_id_text()
)
);
drop policy if exists "Review owners can update tabular cells" on public.tabular_cells;
create policy "Review owners can update tabular cells"
on public.tabular_cells for update
using (
exists (
select 1
from public.tabular_reviews r
where r.id = review_id
and r.user_id = public.current_user_id_text()
)
)
with check (
exists (
select 1
from public.tabular_reviews r
where r.id = review_id
and r.user_id = public.current_user_id_text()
)
);
drop policy if exists "Review owners can delete tabular cells" on public.tabular_cells;
create policy "Review owners can delete tabular cells"
on public.tabular_cells for delete
using (
exists (
select 1
from public.tabular_reviews r
where r.id = review_id
and r.user_id = public.current_user_id_text()
)
);
drop policy if exists "Users can view accessible tabular review chats" on public.tabular_review_chats;
create policy "Users can view accessible tabular review chats"
on public.tabular_review_chats for select
using (
user_id = public.current_user_id_text()
or public.review_is_accessible(review_id)
);
drop policy if exists "Users can insert their own tabular review chats" on public.tabular_review_chats;
create policy "Users can insert their own tabular review chats"
on public.tabular_review_chats for insert
with check (
user_id = public.current_user_id_text()
and public.review_is_accessible(review_id)
);
drop policy if exists "Tabular chat owners can update chats" on public.tabular_review_chats;
create policy "Tabular chat owners can update chats"
on public.tabular_review_chats for update
using (user_id = public.current_user_id_text())
with check (user_id = public.current_user_id_text());
drop policy if exists "Tabular chat owners can delete chats" on public.tabular_review_chats;
create policy "Tabular chat owners can delete chats"
on public.tabular_review_chats for delete
using (user_id = public.current_user_id_text());
drop policy if exists "Users can view accessible tabular chat messages" on public.tabular_review_chat_messages;
create policy "Users can view accessible tabular chat messages"
on public.tabular_review_chat_messages for select
using (
exists (
select 1
from public.tabular_review_chats c
where c.id = chat_id
and (
c.user_id = public.current_user_id_text()
or public.review_is_accessible(c.review_id)
)
)
);
drop policy if exists "Tabular chat owners can insert messages" on public.tabular_review_chat_messages;
create policy "Tabular chat owners can insert messages"
on public.tabular_review_chat_messages for insert
with check (
exists (
select 1
from public.tabular_review_chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
);
drop policy if exists "Tabular chat owners can update messages" on public.tabular_review_chat_messages;
create policy "Tabular chat owners can update messages"
on public.tabular_review_chat_messages for update
using (
exists (
select 1
from public.tabular_review_chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
)
with check (
exists (
select 1
from public.tabular_review_chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
);
drop policy if exists "Tabular chat owners can delete messages" on public.tabular_review_chat_messages;
create policy "Tabular chat owners can delete messages"
on public.tabular_review_chat_messages for delete
using (
exists (
select 1
from public.tabular_review_chats c
where c.id = chat_id
and c.user_id = public.current_user_id_text()
)
);
-- ---------------------------------------------------------------------------
-- Direct client grant hardening
-- ---------------------------------------------------------------------------
--
-- The frontend uses Supabase directly only for authentication. Application
-- data access goes through the backend API with the service role after the
-- backend verifies the user's JWT. Keep RLS enabled and policies defined
-- above as defense in depth, but do not grant the browser anon/authenticated
-- backend verifies the user's JWT. Do not grant the browser anon/authenticated
-- roles direct table privileges for backend-owned data.
revoke all on public.user_profiles from anon, authenticated;