-- ============================================================================ -- Phase V2-2: Row Level Security 정책 -- 패턴: -- - 개인 전용 테이블: user_id = auth.uid() -- - 팀 공유 테이블: 본인 소유 OR 소속 팀의 리소스 -- ============================================================================ -- RLS 활성화 ------------------------------------------------------------------ ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; ALTER TABLE public.teams ENABLE ROW LEVEL SECURITY; ALTER TABLE public.team_members ENABLE ROW LEVEL SECURITY; ALTER TABLE public.meetings ENABLE ROW LEVEL SECURITY; ALTER TABLE public.meeting_memos ENABLE ROW LEVEL SECURITY; ALTER TABLE public.meeting_documents ENABLE ROW LEVEL SECURITY; ALTER TABLE public.transcripts ENABLE ROW LEVEL SECURITY; ALTER TABLE public.history ENABLE ROW LEVEL SECURITY; ALTER TABLE public.dictionary ENABLE ROW LEVEL SECURITY; ALTER TABLE public.memo_tags ENABLE ROW LEVEL SECURITY; ALTER TABLE public.daily_usage ENABLE ROW LEVEL SECURITY; ALTER TABLE public.subscriptions ENABLE ROW LEVEL SECURITY; -- ============================================================================ -- profiles: 본인 프로필만 읽기/수정 -- ============================================================================ CREATE POLICY "profiles_read_own" ON public.profiles FOR SELECT USING (id = auth.uid()); CREATE POLICY "profiles_update_own" ON public.profiles FOR UPDATE USING (id = auth.uid()) WITH CHECK (id = auth.uid()); -- ============================================================================ -- teams: 본인 소유 OR 멤버인 팀만 조회, 본인만 생성/수정/삭제 -- ============================================================================ CREATE POLICY "teams_read_member" ON public.teams FOR SELECT USING ( owner_id = auth.uid() OR id IN (SELECT team_id FROM public.team_members WHERE user_id = auth.uid()) ); CREATE POLICY "teams_insert_own" ON public.teams FOR INSERT WITH CHECK (owner_id = auth.uid()); CREATE POLICY "teams_update_owner" ON public.teams FOR UPDATE USING (owner_id = auth.uid()) WITH CHECK (owner_id = auth.uid()); CREATE POLICY "teams_delete_owner" ON public.teams FOR DELETE USING (owner_id = auth.uid()); -- ============================================================================ -- team_members: 같은 팀 멤버는 서로 조회 가능, 관리는 owner/admin -- ============================================================================ CREATE POLICY "team_members_read_same_team" ON public.team_members FOR SELECT USING ( team_id IN (SELECT team_id FROM public.team_members WHERE user_id = auth.uid()) ); CREATE POLICY "team_members_insert_admin" ON public.team_members FOR INSERT WITH CHECK ( team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') ) ); CREATE POLICY "team_members_update_admin" ON public.team_members FOR UPDATE USING ( team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') ) ); CREATE POLICY "team_members_delete_admin_or_self" ON public.team_members FOR DELETE USING ( user_id = auth.uid() -- 본인 탈퇴 OR team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') ) ); -- ============================================================================ -- meetings: 본인 또는 소속 팀의 회의 -- ============================================================================ CREATE POLICY "meetings_read" ON public.meetings FOR SELECT USING ( user_id = auth.uid() OR (team_id IS NOT NULL AND team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() )) ); CREATE POLICY "meetings_insert" ON public.meetings FOR INSERT WITH CHECK (user_id = auth.uid()); CREATE POLICY "meetings_update" ON public.meetings FOR UPDATE USING ( user_id = auth.uid() OR (team_id IS NOT NULL AND team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') )) ); CREATE POLICY "meetings_delete" ON public.meetings FOR DELETE USING (user_id = auth.uid()); -- ============================================================================ -- meeting_memos: 회의와 동일한 권한 (meeting RLS 경유) -- ============================================================================ CREATE POLICY "meeting_memos_read" ON public.meeting_memos FOR SELECT USING ( meeting_id IN (SELECT id FROM public.meetings) ); CREATE POLICY "meeting_memos_insert" ON public.meeting_memos FOR INSERT WITH CHECK ( user_id = auth.uid() AND meeting_id IN (SELECT id FROM public.meetings WHERE user_id = auth.uid() OR team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() )) ); CREATE POLICY "meeting_memos_update_own" ON public.meeting_memos FOR UPDATE USING (user_id = auth.uid()); CREATE POLICY "meeting_memos_delete_own" ON public.meeting_memos FOR DELETE USING (user_id = auth.uid()); -- ============================================================================ -- meeting_documents: 동일 패턴 -- ============================================================================ CREATE POLICY "meeting_documents_read" ON public.meeting_documents FOR SELECT USING ( meeting_id IN (SELECT id FROM public.meetings) ); CREATE POLICY "meeting_documents_insert" ON public.meeting_documents FOR INSERT WITH CHECK ( user_id = auth.uid() AND meeting_id IN (SELECT id FROM public.meetings WHERE user_id = auth.uid() OR team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() )) ); CREATE POLICY "meeting_documents_update" ON public.meeting_documents FOR UPDATE USING ( user_id = auth.uid() OR meeting_id IN ( SELECT id FROM public.meetings WHERE team_id IN ( SELECT team_id FROM public.team_members WHERE user_id = auth.uid() AND role IN ('owner', 'admin') ) ) ); CREATE POLICY "meeting_documents_delete_own" ON public.meeting_documents FOR DELETE USING (user_id = auth.uid()); -- ============================================================================ -- transcripts: 회의 RLS 경유 -- ============================================================================ CREATE POLICY "transcripts_read" ON public.transcripts FOR SELECT USING ( meeting_id IN (SELECT id FROM public.meetings) ); CREATE POLICY "transcripts_insert" ON public.transcripts FOR INSERT WITH CHECK ( meeting_id IN (SELECT id FROM public.meetings WHERE user_id = auth.uid()) ); CREATE POLICY "transcripts_update" ON public.transcripts FOR UPDATE USING ( meeting_id IN (SELECT id FROM public.meetings WHERE user_id = auth.uid()) ); CREATE POLICY "transcripts_delete" ON public.transcripts FOR DELETE USING ( meeting_id IN (SELECT id FROM public.meetings WHERE user_id = auth.uid()) ); -- ============================================================================ -- history, dictionary, memo_tags, daily_usage: 본인 전용 -- ============================================================================ CREATE POLICY "history_own" ON public.history FOR ALL USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid()); CREATE POLICY "dictionary_own" ON public.dictionary FOR ALL USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid()); CREATE POLICY "memo_tags_own" ON public.memo_tags FOR ALL USING (user_id = auth.uid()) WITH CHECK (user_id = auth.uid()); CREATE POLICY "daily_usage_read_own" ON public.daily_usage FOR SELECT USING (user_id = auth.uid()); -- daily_usage INSERT/UPDATE는 Edge Function(service role)만 수행 -- 일반 유저는 읽기만 가능 -- ============================================================================ -- subscriptions: 본인 읽기만. Stripe webhook이 service role로 쓰기 -- ============================================================================ CREATE POLICY "subscriptions_read_own" ON public.subscriptions FOR SELECT USING (user_id = auth.uid());