ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Moderator 应用 `/admin` 授权巡检(Grant Pass)实操指南:页面授权与动作授权的双轴模型与故障排查

Moderator 应用 `/admin` 授权巡检(Grant Pass)实操指南:页面授权与动作授权的双轴模型与故障排查 Moderator 应用/admin授权巡检Grant Pass实操指南页面授权与动作授权的双轴模型与故障排查【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai本指南围绕 Civitai Moderator 应用apps/moderator的一次关键人工运维动作——对/admin授权界面进行逐项核对grant pass——展开完整梳理页面授权Page Grant与动作授权Action Grant双轴模型的设计动机、静默清零事故的根因以及 8 个动作权限与页面树的逐项勾选清单与验证方法。读完你将掌握如何在权限被拆分为两条独立轴之后正确地核对、授予并验证每个权限识别未重定向的 grant 行无默认值导致全员无权限admin 绕过导致检查失效三类典型陷阱。本文事实来源关联文档 action-grants-review.md 与 page-feature-permissions.md权限模型设计、mod-studio-feedback-2026-08-19.md反馈项上下文以及 permissions.ts、access.ts、hooks.server.ts、page-access.ts 等源码。为什么这是一次紧急巡检而非日常整理2026-08-19 的提交e14a5428dd将页面授权与动作授权彻底拆开并移除了defaultRoles默认角色机制。移除是故意的对未被授权的页面做默认种子写入正是最初导致五个权限被静默清零的原因。其直接后果是——每一个动作授权action grant在被手动勾选之前都处于无人持有状态且没有任何系统会报告这一点。三个仍处于打开状态的反馈项都依赖本次巡检完成后才能诊断因此它不是可做可不做的维护项。从源码看defaultRoles时代的故障链完整记录在 page-feature-permissions.md 的 What this replaced, and why 一节旧实现给每个权限声明了path所在页面与requires依赖的页面列表校验时要求角色授予 全部依赖页面同时满足/users在NAVIGATION中声明却从未真正实现至今仍是 Not built yet 占位页于是没有人持有它五个权限与无人持有的页面求交集后种子化为空数组[]而已存在的行不会被重新种子化于是它们永久保持为空即便在/admin上勾选也不生效——每次保存时子集修剪规则会再次把每个权限裁剪回持有其依赖页面的角色请求期canUse还会再查一次页面所以即使授予正确也无济于事admin 角色绕过全部检查因此谁检查都正常故障在非管理员视角下持续了数周。结果是身份编辑、moderator 开关、Buzz 发送/扣减、批量封禁对每个非 admin 全部失效无人报告唯一声明requires: []的user.buzz.bank反而正常工作——这是代码库中罕见的自然实验。本次巡检就是为这类静默故障画上句号的执行清单。一、先理解双轴模型页面授权与动作授权两条相互独立的授权轴在动作执行处组合绝不在声明处耦合page-feature-permissions.md页面授权 Page Grant动作授权 Action Grant回答的问题角色可以打开哪些页面角色可以执行哪些操作声明位置NAVIGATIONaccess.tsPERMISSIONSpermissions.ts存储形式AppPageAccess行按路由路径为键AppPageAccess行键为grant:id强制位置hooks.server.ts集中式、先于任何 handler动作处对locals.grants校验授予位置/admin第一张表/admin第二张表权限声明只有{ id, label }两个字段不声明任何页面因此同一个权限可以被多个页面复用export const PERMISSIONS [ { id: user.buzz.send, label: Send or deduct Buzz }, // … ] as const satisfies readonly { id: string; label: string }[];带点的id是权限的唯一名字——同一个字面量同时出现在 grant 行、/admin复选框和每个调用点一次grep就能回答谁持有它、这个用户有没有它。PermissionSet采用扁平结构PartialRecordPermissionId, true刻意不做嵌套嵌套会让某个分支一个权限都没持有的用户在访问中间节点时抛异常permissions.user.buzz.send对缺失者直接 THROW而扁平结构下缺失键读取为false闸门默认关闭fail closed这正是必须的行为。关键存储细节PERMISSION_PREFIX grant:permissions.ts。所有 grant 行键为grant:id而此前2026-08-19 之前键是capability:id。id 是存储值而非标签——改动它会让已存的行被静默孤立该权限不是被撤销而是再也找不到表现为某个权限莫名失效且无任何原因。两条轴如何组合requiresGrant封装在 access.ts 中requiresGrant(id, handler)把权限校验做成动作签名的一部分——缺一个守卫在 code review 中不可见而缺一个包装则立刻可见export function requiresGrantE extends RequestEvent, R( id: PermissionId, handler: (event: E) R ) { return (event: E): R | ActionFailure{ scope: denied; error: string } event.locals.grants[id] ? handler(event) : fail(403, { scope: denied as const, error: denied(id) }); }注意它返回fail而不是throw error()抛出会渲染最近的错误边界卸载页面并丢失操作者正在进行中的编辑。实际调用点分布在各路由表单动作中例如user-lookup/[section]/page.server.tsupdateIdentity→user.identity.edit、toggleModerator→user.moderator.toggle、grantCosmetic→user.cosmetics.grant、sendBuzz→user.buzz.sendbulk-ban/page.server.tsbanAll→bulk-ban.executeaudit/generator-restrictions/page.server.ts 与 audit/training-models/page.server.tsban→audit.ban.executeaudit/training-data/[versionId]/page.server.tsreportCsam→csam.report.file。拒绝文案统一由denied(id)生成This action requires the … permission.文案直接取自复选框的label避免手写拒绝语与授权界面漂移——历史上曾有三处手写文案指向了授权界面根本不存在的权限收到拒绝的 moderator 找不到该去勾哪个框。二、步骤 1确认前缀重命名真正落地grant 行的键现在是grant:id直到 2026-08-19 之前它们是capability:id。迁移时仅有的六行是通过手工重定向完成的。核对两点不再有任何行残留在capability:前缀下每个被重定向的行其引用的 id 仍能在本文第三节的权限清单中对应到一个活的 id——一个拼写错误会把行孤立成孤儿效果与未重定向完全一样权限静默失效。这个步骤无法用代码自动化完成因为哪一行该存在本身就是一个业务决策它对应的是谁应当持有什么。而一个未被重定向的行不会被撤销它只是不再被找到——这正是它危险的地方。三、步骤 2逐个勾选 8 个动作授权共 8 个权限。在勾选之前每个都由无人持有。其中 7 个在 UI 上表现为隐藏或拒绝控件症状是按钮消失而不是报错——唯一的例外是user.buzz.bankBuzz 历史正常渲染只是悄悄省略了银行交易行屏幕上没有任何提示说它们被扣留了。所以它是唯一一个不勾选并对比就无法发现的权限。权限清单id、/admin复选框标签、受控位置与备注权限 id复选框标签受控位置备注user.identity.editEdit email, username display nameUser Lookup ▸ Basic User Information 身份面板的编辑控件旧模型下从未设闸/users页面授权兜底是最可能从未正确工作的一个user.buzz.sendSend or deduct BuzzUser Lookup ▸ Buzz 的发送/扣减表单已在渠道中确认为故意只授予两人勾选给恰好这两人是本次变更而非扩大范围user.buzz.bankSee bank transactions in Buzz historyUser Lookup ▸ Buzz history旧模型下唯一正常工作的权限可能已经正确user.moderator.toggleActivate or deactivate moderatorUser Lookup ▸ Admin 的 Account Actions 面板旧模型下isSenior门控user.cosmetics.grantGrant cosmeticsUser Lookup ▸ Cosmetic Shop挂着一个未决决策Retool 限制为 admin 一人移植版可能允许 staff 授予徽章。勾选前先定宽度bulk-ban.executeRun a mass ban/retool/bulk-ban未授权时页面仍可打开用于调查只是拒绝执行封禁audit.ban.executeBan an account from an audit queue/audit/generator-restrictions与/audit/training-models有意与页面访问分离角色可以拥有队列调查权而不拥有账号终结权csam.report.fileFile a CSAM report/audit/training-data/versionId同上提交 CSAM 报告独立设闸逐一核对时的执行顺序先对照 permissions.ts 确认id与标签文字没有漂移该文件是八项权限与每个复选框标签文字的唯一事实来源再勾选、保存、按上表位置验证控件出现。四、步骤 3勾选页面授权一个没有AppPageAccess行的页面仅对moderator:admin可见。页面的授权语义在 access.ts 的collectGrants中定义。/admin页面树上有两种形态行为不同组GroupsImages、Models、Articles、Audit、Retool。组自身不存储任何授权其复选框是对其下页面的三态批量开关组恰好在其任意一个页面可达时可达。给叶子授权即可。源码中isGrantable明确!!item (!item.children || !!item.sharedAccess)——带子项的链接是段没有可单独授予的东西。/reports单独是sharedAccess一份授权覆盖全部举报队列其子项没有自己的行。授予 Reports 即授予全部子队列collectGrants对sharedAccess项直接记录自身授权后continue不再递归子项。需要执行的具体勾选项/retool/user-lookup授予 staff 角色。这就是整个打开的 P1User Lookup unavailable for the staff role的全部内容——没有代码要写纯粹是/admin上的一次勾选对应 permissions-handoff.md 的移交内容。/retool/post-reports2026-08-21 上线勾选前仅moderator:admin可达。它不被/reports的sharedAccess授权覆盖——那份授权只覆盖通用/reports/*队列而这是Retool组下独立的一页。参照谁持有/retool/user-reports来匹配授予。/users/newest2026-08-24 新增勾选前仅 admin 可见。基于自身价值单独授予它读取邮箱和资料文本。切勿顺手授予/users——/users的授权同时是 User Lookup 的封禁、清除与批量评论操作的检查依据源码注释明确/users grant is what User Lookups ban, purge and bulk-comment actions are gated on授予它等于把执法权一并交出去。/users本身仍是它一直以来的 Not built yet 占位页。/users/newest是/users的兄弟节点而非子节点access.tscanAccess取最长匹配授权因此它拥有自己的行。走查NAVIGATION其余部分确认每个预期对角色开放的页面都有行。较新的页面最可能是缺口——按设计新页面上线时就是未授权状态。页面授权的解析与缓存链路页面授权在每次被门控的请求上加载page-access.ts 实现进程内 memo → sysRedis → Postgres三级读穿透memo TTL 30s、Redis TTL 300s失败时allowStale继续提供上次的有效映射——因为空映射与没有任何人被授权不可区分返回空会撤销所有非 admin 的访问。Postgres 是唯一事实来源/admin编辑界面直接读 Postgres 而非请求缓存避免陈旧缓存把复选框渲染成空白、诱使操作者修复性地重新保存而清掉真实授权page-access.ts。底层数据访问在 page-access.db.tsAppPageAccess表getPageAccessGrants/setPageAccessRoles/insertMissingPageAccess/clearPageAccess。五、步骤 4以非管理员身份验证Admin 绕过这一切isSuper在任何allows检查前短路返回 true见 access.ts——这正是最初的故障持续数周的原因任何 admin 检查都正常。这一步才让整个巡检真实生效以一个不是moderator:admin的 moderator 身份登录User Lookup ▸ Chat (DMs)确认页头的聊天链接和every chat this account has posted in链接对非 admin 可解析。该面板是有意仅限 moderator 联系范围的——原先关联的 P0 已关闭属 scope 而非权限真正未验证的是 Chat Audit 本身是否被授权链接指向/retool/chat-audit一个独立的页面授权见 access.ts。reports 页面上的reportedUser不再渲染为灰色。打开的 P1疑似是 User Lookup 访问的下游问题。若授权后仍不恢复则它是独立缺陷——如实上报回到清单成为新的一项。步骤 2 中每个勾选的权限都显示其控件每个未勾选的都以该权限标签对应的措辞拒绝。六、本次巡检关闭的反馈项完成后回到 mod-studio-feedback-2026-08-19.md 勾选对应项——该文件是活跃清单本文件只是工作细节反馈项优先级由谁关闭勾选动作授权然后以非 admin 验证P0本文档全部Chats 面板的链接可到达 Chat Audit—第四节staff 角色无法使用 User LookupP1第四节reports 上reportedUser渲染为灰色P1第五节否则升级为真实缺陷Buzz 发送/扣减显示为无人持有P3 备注第二节 第三节值得复述 mod-studio-feedback-2026-08-19.md 中的两条教训其一两个e14a5428dd引用是巡检前落地的工作它们被刻意标记为打开——因为被相邻改动顺带修好不等于已验证而这里的验证需要一次/admin巡检加一个非 admin 的 moderator其二本轮四个修复中有两个曾被误诊strike 显示被当成显示 bug、Chats 面板被当成权限问题教训是报告者描述的所见是可靠的其猜测的原因只是线索而非结论。附本次巡检的设计边界无代码可写P1User Lookup 对 staff 不可用是纯粹的授权配置/retool/post-reports与/users/newest已上线只差授权行。不要通过修改源码修复这些项。不要新增默认值defaultRoles已被移除任何重新引入种子授权的想法都会重演静默清零事故。新权限/新页面按设计由无人持有开始/admin上的勾选是唯一授予途径。哪些页面存在不是权限问题页面存在性属于NAVIGATION与谁可以打开它正交同理一个动作已在其路由自身的闸门之后权限层无需重复声明页面依赖。完成以上四个步骤后Moderator 应用的双轴授权体系即从理论正确变为实际正确且通过非 admin 视角完成了端到端验证。【免费下载链接】civitaiA repository of models, textual inversions, and more项目地址: https://gitcode.com/GitHub_Trending/ci/civitai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表