跳转到内容
全部文章
约 4 分钟阅读

你的工作区是一条安全边界。

为什么每次读写都要做归属检查,以及为什么任务 URL 绝不应授予访问权。

supastack 团队

在服务端推导归属

记录 ID 标识了某个东西,却不能证明谁可以读它。supastack 按 workspaceId 为用户记录划定边界。服务端解析已认证用户的工作区,并在读取或修改记录时带上这个边界。

不要把浏览器发来的工作区 ID 当作归属证据。浏览器可以请求动作,但可信的服务端上下文必须确定这是谁的动作。该规则对任务状态、已保存的供应商配置、用量记录与每个产品自有资源都必须成立。布局与组件可以过滤渲染什么,但渲染不是授权——检查属于记录真正被读或写的地方。

本文其余部分都出自这条规则:归属是推导出来的,绝不靠请求声明。一旦客户端发来的 workspaceId 在任何地方被信任,其他每个检查就都沦为装饰。认证一文讲了会话本身在这条边界上游如何建立。

成员关系也是边界的一部分

范围回答记录属于哪个工作区;成员关系回答这个用户是否属于那个工作区。两者都在服务端解析。supastack 用 owner、admin、member 三种角色建模个人与组织工作区——工作区指南记录了这个模型。

角色加了第二条轴:member 能读的,可能只有 admin 能改。把两项检查分开——“属于这个工作区”和“可以执行这个动作”——这样降级或移除就在和其他一切相同的边界上生效。邀请与移除属于同一条生命周期,不是管理员的事后补记。

成员关系的变更还必须立即生效。被移除成员的下一个请求应该在边界处失败,而不是等缓存的令牌恰好过期——这也是检查要住在记录被读取的地方,而不是早前签发的凭证里的又一个原因。

测试那些必须失败的请求

一个让所有者取回记录的测试只证明了正常路径,证明不了隔离性。再配上第二个工作区请求同一记录、一个匿名请求,以及一次试图替换所有者的写入。

既断言外部行为也断言持久化结果。被拒绝的请求不得改动其他工作区的数据,错误响应不得泄露密钥或数据库原始细节。这些检查让边界在实现变化时仍可评审。

  • 第二个工作区请求同一个记录 ID——被拒绝。
  • 匿名请求打到同一个端点——被拒绝。
  • 已被移出工作区的成员重试——被拒绝。
  • 一次写入指定了别的所有者或工作区——被拒绝,且没有任何数据被改动。
  • 错误响应保持笼统——不泄露密钥,不泄露 schema 细节。

把规则带进你的产品

新增产品表时,先做界面之前决定它的归属。在持久化模型中包含 workspaceId,通过既有服务端边界解析工作区,并在列表与详情操作中都使用该上下文。

后台 worker 需要同样的谨慎。入队任务必须携带足够的可信上下文以加载正确的记录,而供应商凭证留在服务端。按裸 ID 加载行的定时任务也仍然在这条边界之内。一条一致的归属规则比一堆按路由而异的例外更好维护。

API 端面也属于同一条边界。工作区 API 密钥与签名的出站 Webhook 随身携带着工作区范围——密钥绝不能变成跨租户读取的途径,Webhook 载荷绝不能捎上邻居的记录。API 模块指南讲了这个端面的形状。

关于工作区隔离的常见问题

多租户隔离在每个项目上都会引出同样那几个问题。这几个值得早点定下来。

  • 应用层范围限定够吗,还是需要行级安全? —— 对每次读写都做服务端强制限界,已覆盖大多数 SaaS 的威胁模型。数据库 RLS 是纵深防御的补充,但替代不了边界检查——worker 和 API 密钥仍然需要同一条规则。
  • 跨工作区共享应该怎么做? —— 显式建模——一个邀请、一行成员记录——而不是放宽范围。“任何持有链接的人”应该是一个自带检查的设计功能,而不是一场意外。
  • 多租户应用最先坏掉的是什么? —— 方便捷径:恢复链接、管理员工具、按裸 ID 加载记录的后台 worker。审计每一条绕过正常范围就碰到记录的代码路径。
  • 怎么给已有表加范围限定? —— 加 workspaceId、回填、在读写中强制执行,然后才动 UI。先发布反向测试,让边界是被证明的而不是被假设的。
  • 页面受保护后服务端 action 还需要检查吗? —— 需要。页面守卫只是藏起界面,并不会为操作授权。每个 action 都独立解析会话与工作区。

常见问题

14 个问题

supastack 是什么?

supastack 是用于构建 AI 驱动 SaaS 产品的模块化基础。它把通用基础设施整合在一起,让你专注于产品专属的功能。

它支持 Web 和移动端吗?

支持。supastack 包含 Next.js 与 Expo 应用,作为 Web 与移动端体验的起点。

包含哪些认证方式?

Better Auth 覆盖邮箱/密码、OTP 与社交登录。个人工作区随账户一起开通,产品记录从第一天起就有边界。

AI 是如何支持的?

supastack 包含可配置的多供应商 AI、用量追踪、预算,以及加密的自带密钥(BYOK)支持。

订阅如何处理?

对于基于该套件构建的产品,Stripe 计费、订阅与权限构成付费访问的基础。供应商密钥仅存于服务端。

我的产品专属代码放在哪里?

产品专属功能保留在独立的代码库中,产品通过配置接入 supastack 的基础模块。

supastack 如何定价?

套件为一次性购买、终身更新:单框架 249 美元(限时优惠价,较常规价 349 美元优惠 100 美元),或全框架(Next.js、iOS、Android)499 美元(限时优惠价,较常规价 599 美元优惠 100 美元),每个额外开发者席位 75 美元。套件本身没有订阅。本站结账后通过邮件发送许可证密钥,仓库访问通过 GitHub 邀请授予。

工作区如何界定我的数据?

用户记录按 workspaceId 划定边界。身份、权限、AI 用量、计费事件与审计条目都留在个人工作区边界内。

后台任务如何工作?

Trigger.dev 是明确的任务运行时。JobRunner 契约只承诺 enqueue、status 与 cancel——工作流语义保持 Trigger.dev 专有且显式。

邮件与营销触达有何不同?

事务性邮件覆盖产品工作流,如任务完成通知。冷触达与营销活动属于产品自身的范畴,不在本套件的邮件模块内。

移动端购买如何运作?

移动端已支持 RevenueCat 原生购买和恢复购买,访问权限由服务端验证。发布前需配置商店账户并完成设备测试。Web 计费保持独立。

AI 供应商密钥存放在哪里?

自带密钥凭证经过加密且仅存于服务端。密钥在日志中被脱敏;用量与预算在工作区级别保持可见。

结账支持哪些付款方式?

Stripe 托管结账接受银行卡,在 Stripe 账户启用后还可提供支付宝与微信支付——方便中国买家。钱包授权为异步确认,付款完成后许可证页面会立即更新。

我能申请退款吗?

获得代码仓库访问权限后,supastack 将永久归你所有,因此无法退款。但请放心,你将获得包含终身更新的完整源代码,可为你自己或客户构建并发布无限数量的产品。你只需注意不能转卖代码,也不能将仓库作为启动模板二次分发。

CN