Logo花火漫画开发文档

Row Level Security

数据库行级安全策略总览

所有 public schema 下的 34 张业务表均已启用 RLS。未配置策略的表默认拒绝所有操作(仅 SECURITY DEFINER 函数和 service_role 可绕过)。

辅助函数

策略中大量复用两个权限判断函数,均从 JWT app_metadata.role 字段读取角色:

-- 仅 admin
CREATE FUNCTION is_admin() RETURNS boolean AS $$
DECLARE user_role text;
BEGIN
  user_role := (auth.jwt() -> 'app_metadata' ->> 'role');
  RETURN user_role = 'admin';
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- admin 或 editor
CREATE FUNCTION is_admin_or_editor() RETURNS boolean AS $$
DECLARE user_role text;
BEGIN
  user_role := (auth.jwt() -> 'app_metadata' ->> 'role');
  RETURN user_role IN ('admin', 'editor');
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

部分早期策略直接内联了 (auth.jwt() -> 'app_metadata' ->> 'role') = 'admin' 而非调用辅助函数,效果等价。

策略模式总览

项目中的 RLS 策略遵循以下几种固定模式:

模式说明典型表
公开读 + 管理写所有人可读,admin/editor 可增删改comics, chapters, categories, tags
公开读 + 用户自管理所有人可读,用户只能操作自己的行comic_likes, comic_ratings, comics_favorites
仅用户本人用户只能访问自己的数据notifications, reading_history
公开只读所有人可读,仅 admin 可写announcements, app_settings, products
条件可见根据状态或标志位控制可见性comments, kanban_tickets
无策略(默认拒绝)RLS 已启用但未配置策略,仅函数内部可写chapter_view_logs, free_user_daily_premium_views

漫画内容

comics

公开可读,admin/editor 完全管理。

策略名操作条件
Public read comicsSELECTtrue
Admins and editors can insert comicINSERTis_admin_or_editor()
Admins and editors can update comicUPDATEis_admin_or_editor()
Admins and editors can delete comicDELETEis_admin_or_editor()

chapters

与 comics 相同的管理模式。

策略名操作条件
Public read chaptersSELECTtrue
Admins and editors can insert chaptersINSERTis_admin_or_editor()
Admins and editors can update chaptersUPDATEis_admin_or_editor()
Admins and editors can delete chaptersDELETEis_admin_or_editor()

categories

策略名操作角色条件
Public categories are viewable by everyoneSELECTpublictrue
admin full accessALLpublicis_admin_or_editor()

tags

策略名操作角色条件
Public tags are viewable by everyoneSELECTpublictrue
Allow admin and editor full accessALLauthenticatedapp_metadata.role IN ('admin','editor')

tags 表的管理策略直接内联了 JWT 检查而非调用 is_admin_or_editor(),这是早期写法,功能等价。

comic_tags

策略名操作条件
Public read comic_tagsSELECTtrue
Admins and editors can insert comic_tagsINSERTis_admin_or_editor()
Admins and editors can update comic_tagsUPDATEis_admin_or_editor()
Admins and editors can delete comic_tagsDELETEis_admin_or_editor()

comic_relations

策略名操作条件
Public read relationsSELECTtrue
admins and editor can do everything on relationsALLis_admin_or_editor()

用户交互

comic_likes

公开可读,用户只能操作自己的点赞。

策略名操作条件
Public can read likesSELECTtrue
Users can insert their own likesINSERTauth.uid() = user_id
Users can delete their own likesDELETEauth.uid() = user_id

comic_ratings

策略名操作条件
Public can read ratingsSELECTtrue
Users can insert their own ratingsINSERTauth.uid() = user_id
Users can update their own ratingsUPDATEauth.uid() = user_id
Users can delete their own ratingsDELETEauth.uid() = user_id

comics_favorites

策略名操作条件
public can read favoritesSELECTtrue
User manages own favoritesALLauth.uid() = user_id

comment_likes

策略名操作条件
Public view likesSELECTtrue
Users can likeINSERTauth.uid() = user_id
Users can unlikeDELETEauth.uid() = user_id

comments

评论的可见性受 status 字段控制,用户始终能看到自己的评论(包括被审核的),管理员可见全部。

策略名操作条件
Comments visibility policySELECTstatus = 'public' OR auth.uid() = user_id OR is_admin_or_editor()
Authenticated users can insertINSERTauth.role() = 'authenticated' AND auth.uid() = user_id
Comments update policyUPDATEis_admin_or_editor()
Comments delete policyDELETEauth.uid() = user_id OR is_admin_or_editor()

普通用户可以删除自己的评论,但不能编辑(编辑权限仅限管理员)。这是有意设计,防止用户修改已审核通过的评论内容。


用户与个人资料

profiles

策略名操作条件
Public profiles are viewable by everyoneSELECTtrue
Users can insert their own profileINSERTauth.uid() = id
Users can update own profileUPDATEauth.uid() = id
Admins can update any profileUPDATEis_admin()

user_badges

策略名操作条件
Badges are viewable by everyoneSELECTtrue
Users can only equip valid badgesUPDATEUSING: auth.uid() = user_id,WITH CHECK: is_displayed = false OR expires_at IS NULL OR expires_at > now()
admin has full accessALLis_admin()

用户装备徽章时,WITH CHECK 额外验证徽章未过期,防止装备已失效的徽章。

badge_definitions

策略名操作条件
Everyone can view badgesSELECTtrue

阅读与通知

reading_history

策略名操作条件
User manages own historyALLauth.uid() = user_id

notifications

用户只能访问自己的通知,管理员可发送。

策略名操作条件
Users can view own notificationsSELECTauth.uid() = user_id
Users can update own notificationsUPDATEauth.uid() = user_id
admin and editor can send dmsINSERTis_admin_or_editor()

chapter_view_logs

RLS 已启用但无任何策略,所有客户端操作均被拒绝。写入仅通过 SECURITY DEFINER 函数 count_chapter_view_if_new 完成。详见 章节浏览计数

free_user_daily_premium_views

RLS 已启用但无任何策略,所有客户端操作均被拒绝。写入仅通过 SECURITY DEFINER 函数 record_free_premium_view 完成。详见 高清配额管理


支付

products

策略名操作条件
Everyone can view productsSELECTtrue
Admins can insert productsINSERTis_admin()
Admins and Service Role can update productsUPDATEis_admin() OR auth.role() = 'service_role'
Admins can delete productsDELETEis_admin()

products 的 UPDATE 额外允许 service_role,因为支付回调的 Edge Function 需要用 service_role 更新商品状态。

orders

策略名操作条件
Users view own ordersSELECTauth.uid() = user_id
Admins view all ordersSELECTis_admin()
Users create ordersINSERTauth.uid() = user_id
Service or Admin update ordersUPDATEis_admin() OR auth.role() = 'service_role'

工单系统

kanban_tickets

工单的可见性由 is_public 字段和提交者身份共同控制。

策略名操作角色条件
ticket_selectSELECTanon, authenticatedis_public = true OR reporter_id = auth.uid() OR is_admin_or_editor()
ticket_insert_adminINSERTauthenticatedis_admin_or_editor()
ticket_updateUPDATEauthenticatedUSING/WITH CHECK: (reporter_id = auth.uid() AND status IN ('RECORDED','TRACKING')) OR is_admin_or_editor()
ticket_deleteDELETEauthenticated(reporter_id = auth.uid() AND status IN ('RECORDED','TRACKING')) OR is_admin()

普通用户只能修改/删除自己提交的且状态为 RECORDED 或 TRACKING 的工单,进入后续阶段后仅管理员可操作。删除权限仅限 is_admin()(不含 editor)。

ticket_votes

策略名操作角色条件
select_all_votesSELECTanon, authenticatedtrue
View own votesSELECTauthenticateduser_id = auth.uid()
Vote on public ticketsINSERTauthenticated子查询检查 kanban_tickets.is_public = true
insert_voteINSERTpublicauth.uid() IS NOT NULL AND user_id = auth.uid() + 子查询检查公开且非自己提交的工单
Delete own voteDELETEauthenticateduser_id = auth.uid()
delete_own_voteDELETEpublicuser_id = auth.uid()

ticket_votes 存在重复策略(同一操作有两条 PERMISSIVE 策略),这是迭代过程中的残留,功能上不影响(PERMISSIVE 策略取 OR 关系),但建议后续清理。


活动系统

app_events

策略名操作角色条件
authenticated_read_eventsSELECTauthenticatedis_active = true
admin can do all thingsALLpublicis_admin()

app_event_signups

报名表的 INSERT 策略包含子查询校验活动状态:

策略名操作角色条件
users_read_signupsSELECTpublictrue
users_insert_own_signupINSERTauthenticateduser_id = auth.uid() AND status = 'pending' + 子查询检查活动 is_active 且在时间范围内
users_update_own_pending_signupUPDATEauthenticateduser_id = auth.uid() AND status = 'pending',WITH CHECK 额外要求 reviewed_by/reviewed_at IS NULL
users_delete_own_pending_signupDELETEauthenticateduser_id = auth.uid() AND status = 'pending'
admin can do all thingsALLauthenticatedis_admin()

用户只能操作自己 pending 状态的报名,一旦被管理员审核(reviewed_by 非空),用户即无法修改或删除。

app_channel_members

策略名操作条件
admin_full_accessALLis_admin()

系统配置

app_settings

策略名操作角色条件
Allow public read on app_settingsSELECTpublictrue
Public read accessSELECTpublictrue
Admin write accessALLauthenticatedis_admin()

announcements

策略名操作条件
Allow public read on announcementsSELECTtrue
admin can edit any announcementsALLis_admin()

home_banners

策略名操作条件
Allow public read on home_bannersSELECTtrue

app_versions / app_release_infos

两张表策略完全相同:公开可读,admin 完全管理。

策略名操作条件
Public read versions / Public read releasesSELECTtrue
Only admins can insert ...INSERTis_admin()
Only admins can update ...UPDATEis_admin()
Only admins can delete ...DELETEis_admin()

admin_logs

策略名操作条件
Admins can view logsSELECTapp_metadata.role = 'admin'

download_tasks

策略名操作条件
public readSELECTtrue
all staff can manage webhookALLis_admin_or_editor()

download_task_chapters

策略名操作条件
all-approve webhookALLis_admin_or_editor()

anon_sd_access_log

策略名操作条件
Allow select from edge functionSELECTtrue
Allow insert from edge functionINSERTtrue

anon_sd_access_log 的 INSERT 策略为 true(允许所有人写入),这是因为匿名用户的 SD 图片访问记录由 Edge Function 以 anon key 写入,无法使用 auth.uid() 约束。

On this page