소개
지난 포스트에 이어 supabase todo 예제를 통해 RLS(Row Level Security) 기능을 살펴 봅니다.
RLS(Row Level Security) 란
Row Level Security (RLS)는 데이터베이스 수준에서 행(row) 단위의 접근 제어를 가능하게 하는 보안 기능입니다. BaaS인 Supabase는 별도의 back end 프로그램 없이 PostgreSQL의 RLS 기능을 활용하여 비지니스 로직을 구현합니다.
먼저 공개 접근이 가능한 end point와 리버스 엔지니어링으로 유출 가능한 anon
로 접근하는 Supabase인 점을 생각해 봅니다.
다시말해 누구든 Postgres에 있는 데이터에 접근이 가능하고 CRUD 작업을 할 수 있게 됩니다.
Todo 서비스의 경우 Todo를 만든 사용자의 기준으로 목록이 보여지고 수정 및 삭제가 가능해야 할 것 입니다.
이를 위해 먼저 Supabase는 Authentication이라는 기능을 기본으로 지원하여 사용자 식별을 합니다.
그뒤 로그인한 사용자 정보를 바탕으로 RLS 정책을 생성하도록 합니다.
따라서 별도의 backend 프로그램 개발, 배포, 운영 없이도 비지니스 로직을 구현할 수 있습니다. 추가로 데이터 베이스 수준의 보안이 이루어 짐으로 서비스의 신뢰성도 높아 집니다.
Todo 삭제 정책 설계
이전 프로그램은 Todo를 작성한 사용자가 자신의 Todo 아이템을 삭제할 수 있습니다.
삭제에 있어서 다음과 같은 RLS 정책을 추가해 볼 수 있을 것 입니다.
특정 시간이 지난 후에만 삭제 가능하도록
create policy "Can only delete todos after 24 hours" on todos for
delete using (
auth.uid() = user_id and
(now() - inserted_at) > interval '24 hours'
);
완료한 todo만 삭제 가능하도록
create policy "Can only delete completed todos" on todos for
delete using (
auth.uid() = user_id and
is_complete = true
);
하루에 삭제할 수 있는 수를 제한
create policy "Can only delete 5 todos per day" on todos for
delete using (
auth.uid() = user_id and
(
select count(*)
from todos
where user_id = auth.uid()
and deleted_at >= date_trunc('day', now())
) < 5
);
특정 조건의 todo만 삭제 가능하도록
create policy "Can only delete short todos" on todos for
delete using (
auth.uid() = user_id and
char_length(task) <= 100
);
완료한 todo만 삭제 가능하도록 Todo 삭제 정책 추가하기
위의 여러 기능 중 완료한 todo만 삭제 가능하도록 정책 설정을 해 보겠습니다.
먼저 npm i -D supabase
명령으로 CLI 패키지를 인스톨합니다.
이는 supabase CLI 툴이며, npx supabase --help
로 자세한 명령세트를 확인할 수 있습니다.
npx supabase link
명령을 사용하면 supabase 프로젝트와 연동할 수 있습니다. 이때 supabase/config.toml
를 업데이트 하라는 메시지가 나오며, 필요에 따라 직접 수정해 줍니다.
예제의 초기 migration 파일은 다음과 같습니다.
create table todos (
id bigint generated by default as identity primary key,
user_id uuid references auth.users not null, -- auth schema에 있는 users 테이블과 연관을 맺습니다.
task text check (char_length(task) > 3), -- 3 글자 이상이어야 합니다.
is_complete boolean default false,
inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table todos enable row level security;
create policy "Individuals can create todos." on todos for
insert with check (auth.uid() = user_id);
create policy "Individuals can view their own todos. " on todos for
select using (auth.uid() = user_id);
create policy "Individuals can update their own todos." on todos for
update using (auth.uid() = user_id);
create policy "Individuals can delete their own todos." on todos for
delete using (auth.uid() = user_id);
위의 SQL은 초기 deploy 과정에서 실행한 상태이며, Authentication > Policies
화면에서 다음과 같이 확인할 수 있습니다.
이제 완료한 경우에만 삭제가 가능하도록 변경해 주어야 합니다.
create policy "Can only delete completed todos" on todos for
delete using (
auth.uid() = user_id and
is_complete = true
);
다음과 같이 RLS 정책을 UI를 통해 생성할 수도 있습니다.
하지만 이 경우, 데이터 베이스의 수정 사항을 기록에 남겨 혹시있을 재설치 및 트러블 슈팅이 어려울 수 있습니다.
따라서 테이블 및 RLS 정책 생성 등 DB에 가해지는 변경이 필요할 경우, UI를 사용하는 것 보다는 migration 기능을 활용하는 것이 좋습니다.
먼저 npx supabase migration new add_delete_policy_for_todos
명령으로 빈 migration 파일을 생성합니다.
Supabase CLI는 {프로젝트 root}/supabase/migrations/{timestamp}_add_delete_policy_for_todos.sql
와 같은 형식으로 파일을 생성해 줍니다.
이 곳에 RLS 정책을 설정하는 SQL 구문을 작성합니다.
-- 기존 정책 삭제
drop policy if exists "Individuals can delete their own todos." on todos;
-- 새로운 정책 추가
create policy "Can only delete completed todos" on todos for
delete using (
auth.uid() = user_id and
is_complete = true
);
npx supabase db push
명령을 수행하여 새로 추가한 migration 파일의 SQL 구문을 DB에 적용합니다.
대쉬보드의 Database > Migrations
화면에서 다음과 같이 확인할 수도 있습니다.
feat: RLS: 완료 했을 경우에만 삭제 가능 · swcho/supabase-nextjs-todo-list@bee1598
테스트 케이스
Supabase의 비지니스 로직은 사실상 RLS와 함수 등으로 구현하기 때문에 자주 변경이 이루어질 수 밖에 없습니다.
특별히 비지니스 로직은 여러 사용자가 있는 시나리오에 대해서 세심하게 설계하고 충분히 검증해야 합니다.
따라서, 비지니스 로직 자체를 문서화 하고 검증할 수 있는 test case를 가져가는 것이 best practice가 될 것입니다.
vitest
먼저 vitest를 설정 합니다.
npm i -D vitest @vitejs/plugin-react vite-tsconfig-paths dotenv @testing-library/jest-dom
vitest.config.mts 파일을 생성하고, test/setup.ts을 통해 Supabase의 end point와 anon 키 정보를 가지는 환경 변수를 로드 합니다.
테스트 계정 생성
테스트 코드 수행에 사용할 테스트 계정을 별도로 생성해 줍니다.
사용자는 Add user -> Create a new user
기능을 활용해 UI 상에서 직접 생성할 수 있습니다.
테스트 코드
테스트 코드는 다음과 같습니다.
- 사용자 로그인
- Todo 생성
- 삭제 시도 후 오류 확인
- Todo complete 설정
- 삭제 시도 후 정상 동작 확인
마치며
Supabase의 RLS 기능을 실제로 todo 삭제 시나리오에 적용해 보고 그 과정을 학습해 보았습니다.
또한 앞으로 있을 RLS 기능의 업데이트에 따른 비지니스 로직의 동작 검증을 위한 테스트 코드도 짜 보았습니다.
별도의 서버 구현없이 비지니스 로직을 구현하고 서비스에 적용할 수 있는 점에서 Supabase의 독특함과 매력을 느낄 수 있었습니다.