feat: add supabase jwt auth context

This commit is contained in:
Codex
2026-06-29 00:17:29 +08:00
parent fbcfa5127e
commit 9553836ac7
20 changed files with 452 additions and 85 deletions

View File

@@ -62,9 +62,9 @@ types.ts 仅本领域使用的类型
- 可预期错误用 `HttpError`,生产环境不向前端暴露内部异常。
- 写接口必须考虑幂等、审计和租户隔离;支付 webhook 必须先设计幂等键。
- 不要为了少写 API 而让前端直写复杂业务表。新增前端直连 table/view/RPC 必须先满足 RLS、最小 grant、跨租户测试、权限测试和索引要求。
- 迁移期接口可用 `x-user-id` 标识学生用户;接 Supabase Auth 后统一替换为 JWT 解析
- 登录类接口先使用 `Authorization: Bearer tk_*` 迁移期 sessionsession 明文只返回客户端,数据库只保存 hash。
- 平台运营接口使用 `x-platform-admin-key` 作为临时保护;正式上线前要迁到平台管理员 JWT 和审计日志
- 新接口优先使用 `Authorization: Bearer <supabase_access_token>`;后端通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射业务身份
- 迁移期仍支持 `Authorization: Bearer tk_*` sessionsession 明文只返回客户端,数据库只保存 hash。
- `x-user-id` `x-platform-admin-key` 只允许在非生产兼容模式使用;生产必须关闭 `ALLOW_LEGACY_AUTH_HEADERS``ALLOW_PLATFORM_ADMIN_KEY`
- `platform-admin` 管平台与合作商之间的 SaaS 账务,`tenant-admin` 管合作商自己的品牌、域名、公开配置、登录/商户配置、活动和兑换码,`tenant-content` 管合作商自己的题库和学习内容维护。
- `tenant-admin` 的敏感配置必须拆分:公开字段进入 `config_public`商户密钥、短信密钥、OAuth app secret 进入 `app_private.tenant_secrets` 或生产 KMS/Vault对前端只返回 `secretRef` 和掩码状态。
- `tenant-admin` 权限由 `tenant_memberships.role` 的默认权限和 `permissions` JSON 覆盖共同决定;后端接口必须校验具体权限点,不能只依赖前端菜单隐藏。

View File

@@ -15,7 +15,7 @@
| 模块 | 状态 | 说明 |
| --- | --- | --- |
| Supabase/PostgreSQL schema | 可联调 | `supabase/migrations` 已包含多租户、题库、学习、订单、内容、CRM、平台账务等表 |
| RLS/租户隔离 | 迁移期 | 表层普遍有 `tenant_id` 和 RLS 策略API 已接入 session 优先身份上下文;生产前继续补 Supabase JWT/RLS 回归 |
| RLS/租户隔离 | 可联调 | 表层普遍有 `tenant_id` 和 RLS 策略API 已支持 `tk_` 迁移 session 与 Supabase Auth JWT 双入口,并覆盖跨租户/伪造身份集成测试;生产前继续补真实云端 JWT/RLS 回归 |
| API 分层 | 可联调 | `apps/api/src/core` + `apps/api/src/features/*` |
| Docker API | 可联调 | `docker-compose.api.yml``apps/api/Dockerfile` 可用 |
| 测试 | 可联调 | `npm run check:refactor` 覆盖 TS 检查、导入校验、seed、API 集成测试 |
@@ -37,9 +37,10 @@
| --- | --- | --- |
| 短信验证码登录 | 可联调 | 已有验证码、冷却、hash、登录事件支持 mock、阿里云短信、腾讯云短信 provider生产仍需真实账号联调 |
| 迁移期 session | 迁移期 | `tk_` token hash 存在 `app_private.auth_sessions`,用户态接口已优先解析 bearer session 并拒绝伪造 userId/tenantId |
| Supabase Auth JWT | 可联调 | API 已用 Bearer JWT 验签并通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射业务身份;支持 HS256 JWT secret 或 JWKS测试覆盖学生、租户管理员、平台管理员、错租户、坏签名 |
| 微信小程序登录 | 可联调 | `/api/auth/oauth/wechat-miniapp` 已接 `code2Session`、openid/unionid 身份、session 签发和登录审计 |
| 微信网页/QQ OAuth | 待补齐 | 目前仍是 placeholder需要 code 换 token、回调域名、账号合并和审计 |
| 平台管理员鉴权 | 迁移期 | `x-platform-admin-key` 已可通过配置禁用;生产前必须换平台管理员 JWT/服务端会话 |
| 平台管理员鉴权 | 可联调 | 已支持平台管理员 Supabase JWT`x-platform-admin-key` 仅作本地/迁移期兼容且可通过配置禁用 |
| 租户角色权限 | 可联调 | `tenant_memberships.role + permissions`,接口有权限点校验 |
| 自定义角色模板 | 待补齐 | 当前有权限 JSON 覆盖,缺角色模板、菜单/模块/字段级权限配置 UI/API |

View File

@@ -20,7 +20,7 @@
| 模块 | 当前状态 | 已经具备 | 上线前还要补 |
| --- | --- | --- | --- |
| 多租户底座 | 基础完成 | 租户、域名、品牌、设置、RLS 基础、审计 | Supabase Auth/JWT 替换迁移期请求头,生产 RLS 回归 |
| 多租户底座 | 可联调 | 租户、域名、品牌、设置、RLS 基础、审计Supabase JWT/API 身份映射 | 真实云端 Auth/JWKS 回归、生产 RLS 深测 |
| 平台后台 | 基础完成 | 租户、套餐、订阅、账单、服务费、用量 | 自动计费、平台审计、公共题库披露策略 |
| 租户后台 | 基础完成 | 品牌、域名、支付账户、登录配置、密钥掩码、活动、兑换码、优惠券、成员权限 | 自定义角色模板、菜单/模块可见性 UI、字段级权限 |
| 题库与练习 | 可联调 | 内容入口、任意深度分类、题目集合、顺序/随机/全真模拟蓝图、组卷快照、答题、错题、收藏 | 完整模考交卷报告、专项策略、公题库采纳/授权、Excel 导入 |
@@ -75,7 +75,7 @@
### P0上云测试和前端主链路前必须处理
- 生产鉴权: Supabase Auth/JWT 或服务端 session 替换 `x-tenant-id``x-user-id``x-platform-admin-key`
- 生产鉴权:API 已支持 Supabase Auth JWT;继续做真实云端 Auth/JWKS 回归、RLS 深测,并在生产关闭 `x-user-id``x-platform-admin-key` 兼容入口
- 对象存储:上传/下载签名已接入阿里云 OSS、腾讯云 COS、Supabase Storage继续完成上传后对象校验、PDF 预览、视频播放签名、防盗链和水印。
- 真实数据 dry-run导出 PocketBase 用户、题库、单词、知识手册、分数线、订单、权益,跑迁移和校验报告。
- 生产环境配置:补 `.env` 模板、数据库迁移流程、备份恢复、日志、告警和 API 容器部署说明。

View File

@@ -17,6 +17,7 @@
- `tenant-admin`:租户资料、品牌、公开设置、域名、支付账户、登录 provider、私密密钥掩码、活动内容、激活码批次、优惠券、成员管理、权限矩阵、审计查询。
- `tenant-content`:租户后台内容入口、任意深度分类树、考试意向标记、题目集合、练习蓝图、题目、视频、分数线、单词、知识手册、资料资源、题目/单词/知识手册 JSON 导入维护。
- `tenant`:域名/租户解析。
- 鉴权上下文已支持 Supabase Auth JWT 和迁移期 `tk_` session 双入口JWT 通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射业务用户和租户;平台管理员 JWT 已可访问平台后台。
- `learning` 已接入商用访问控制免费用户每日题量、SVIP 范围、SVIP-only 内容、答题 session 快照保护由后端强制执行。
- `src/services/supabaseApi.ts` 已加入新 API 客户端方法,供旧 Web 逐步替换和后续 Taro 复用。
- 已新增 `npm run db:smoke-seed`,用于 `supabase:reset` 后恢复最小烟测数据。
@@ -165,10 +166,11 @@ GET /api/tenant-admin/audit-logs
## 迁移期约定
- 当前写接口用 `x-tenant-id``x-user-id` 做迁移期上下文
- `auth` 当前签发迁移期 `tk_` sessiontoken hash 存在 `app_private.auth_sessions`;后续接 Supabase Auth 后,`x-user-id` 要替换为 JWT 用户身份解析
- 当前 API 已支持 Supabase Auth JWT 和迁移期 `tk_` session新前端应优先使用 `Authorization: Bearer <supabase_access_token>`
- `x-tenant-id` 只作为租户上下文,后端会校验 JWT/session 用户确实属于该租户;`x-user-id` 只允许在非生产兼容测试中使用
- `auth` 当前仍可签发迁移期 `tk_` sessiontoken hash 存在 `app_private.auth_sessions`,用于旧数据迁移和本地联调。
- 短信验证码只保存 HMAC hash不保存明文本地 `mock` provider 才会返回 `debugCode`
- `platform-admin` 当前用 `x-platform-admin-key` 做迁移期保护,生产后必须替换为平台管理员 JWT/服务端会话
- `platform-admin` 已支持平台管理员 Supabase JWT`x-platform-admin-key` 只作为非生产兼容保护
- B 端合作商年费/服务费使用 `tenant_invoices``tenant_invoice_items``tenant_invoice_payments`,不与 C 端学生订单混表。
- 订单金额以后端套餐价格为准,不信任前端传价。
- 激活码兑换和支付成功都走同一套 `grantSvipEntitlement` 权益开通逻辑。

View File

@@ -33,12 +33,13 @@
- `question_collections`
- `practice_blueprints`
- 可以接入迁移期短信登录和 `tk_` session用于本地/内网联调。
- H5 可以直接用 Supabase Auth access token 调 `apps/api`;后端已支持 JWT 验签和业务用户映射。
- H5 可以优先验证 `@supabase/supabase-js` 管理 Auth session微信小程序端先验证运行时兼容性业务数据默认仍走 `apps/api`
- 可以接入租户品牌、主题、功能开关和域名/小程序参数解析。
## 不能误认为已商用完成的部分
- 生产鉴权尚未完成:当前很多接口仍用 `x-tenant-id``x-user-id``x-platform-admin-key` 作为迁移期上下文
- 生产鉴权已具备 Supabase JWT API 入口,但仍要做真实云端 Auth/JWKS 回归、RLS 深测和自定义角色权限细化;前端不要继续使用 `x-user-id`
- 不要把“Supabase 支持前端 Data API”误解为“本项目所有业务表都由 Taro 直写”订单、支付、权益、租户后台、导入、CRM、私有资源必须走 RPC、`apps/api`、Edge Function 或 worker 这类后端命令层。
- 真实短信、微信登录、QQ 登录、微信支付、支付宝支付 provider 还未正式接完。
- 对象存储已完成签名 provider但 PDF 预览、防盗链、视频水印、上传后校验还要补。

View File

@@ -37,8 +37,8 @@
| 活动/优惠 | 已建优惠券、激活码、激活码批次、banner、FAQ、公告等基础表 | 部分支持 | banner/FAQ/公告只读与租户后台维护、激活码兑换、激活码批次、批量生成激活码、优惠券维护已实现 | 核心 API 集成测试 | 基础运营后台可用,复杂活动规则、营销自动化、核销报表待补 |
| 销售/代理客资追踪 | 已建推荐码、首绑客资、团队关系、小程序码缓存、CRM 队列 | 旧 `referral_tracks` 已有映射基础 | 邀请码、扫码/分享事件、首绑保护、销售统计、客资明细、手动补绑、团队关系、CRM 配置/队列已实现 | 核心 API 集成测试 | 增长链路基础可用真实微信小程序码、分佣结算单、CRM worker 推送待补 |
| 租户后台 | 已建品牌、域名、设置、支付账户、登录 provider、私密密钥表、成员、审计日志、资源台账、导入台账、内容导航台账 | 不适用 | 概览、品牌、设置、域名、支付账户、登录配置、密钥掩码、活动内容、兑换码/优惠券、成员管理、权限矩阵、审计查询、内容入口/分类树/题目集合/练习蓝图维护、资源管理、题目 JSON 导入已实现 | 核心 API 集成测试含角色/权限/租户隔离/密钥不泄露/导航/组卷/资源与导入断言 | 租户配置与运营闭环可用,前端权限 UI、Excel 导入、真实对象存储签名待补 |
| 平台后台 | 已建 SaaS 套餐、订阅、账单、服务费、用量 | 不适用 | 租户管理、账单、收款确认、用量记录已实现 | 仅烟测 | 平台收费链路骨架可用,正式鉴权/审计/自动计费未完成 |
| 登录认证 | 已建短信验证码、会话、OAuth provider 配置表 | 旧用户映射已预留 | 短信 mock 登录、迁移期 session、OAuth 占位已实现 | 仅烟测 | 本地可测,真实短信/微信/QQ 登录未完成 |
| 平台后台 | 已建 SaaS 套餐、订阅、账单、服务费、用量 | 不适用 | 租户管理、账单、收款确认、用量记录、平台管理员 Supabase JWT 鉴权已实现 | API 集成测试 | 平台收费链路骨架可用,平台审计报表/自动计费待补 |
| 登录认证 | 已建短信验证码、会话、OAuth provider 配置表,并支持 `auth_user_id` 映射 | 旧用户映射已预留 | 短信 mock 登录、迁移期 session、Supabase JWT 验签映射、微信小程序登录主链路已实现 | API 集成测试 | H5 Supabase Auth 可联调;真实短信/微信网页/QQ 登录生产联调待补 |
| 数据导入 | 已建立 importer、risk report、validate | 已覆盖多类旧集合 | 命令行导入/校验 | `pb:import:validate` | 基础工具可用,需用真实完整数据做多轮 dry-run |
| 测试体系 | 不适用 | 不适用 | 不适用 | 已新增核心 API 集成测试、租户隔离测试、权限矩阵测试、资源/题目导入测试、导入校验 | 还不是完整覆盖,支付幂等、真实导入回归、前端端到端测试仍需补 |
@@ -238,7 +238,7 @@ platform-admin:
上线前至少还需要完成:
1. 正式鉴权:迁移期 `x-tenant-id``x-user-id``x-platform-admin-key` 要替换为 Supabase Auth/JWT/服务端 session并逐表验证 RLS
1. 正式鉴权:API 已支持 Supabase Auth JWT生产前继续做真实云端 Auth/JWKS 回归、RLS 深测,并关闭 `x-user-id``x-platform-admin-key` 兼容入口
2. 国内能力接入短信、微信登录、微信小程序登录、QQ 登录、微信支付、支付宝支付的租户级配置入口已具备,但真实 provider adapter、回调验签和 webhook 幂等仍需实现。
3. 核心缺口 API学生端个人中心、分数线、题目视频详情、背单词进度/收藏已补基础 API下一步重点是后台维护、权限、统计和真实业务验收。
4. 后台能力:题库录入、题目/单词/知识手册 JSON 批量导入、资源台账、视频绑定、知识手册维护、分数线维护、品牌/商户/登录/活动/兑换码配置、销售客资、CRM 队列、成员权限、审计查询已补 APIExcel 导入、分数线/视频导入、真实对象存储签名和前端操作台待补。

View File

@@ -88,7 +88,7 @@
### P0前端联调到云端前
1. 正式鉴权Supabase Auth/JWT 或服务端 session替换迁移期请求头
1. 正式鉴权:API 已支持 Supabase Auth JWT;继续补真实云端 Auth/JWKS 回归、RLS 深测和自定义角色权限细化
2. 生产配置 fail-fast默认密钥、`CORS=*`、mock SMS、平台默认 key 必须禁止。
3. JSON body size limit导入接口可配置更大限制但必须有上限。
4. 真实租户隔离测试:跨租户读写、角色越权、资源下载越权。

View File

@@ -20,6 +20,9 @@
当前后端已经进入“session 优先、迁移头受控兼容”的状态:
- `Authorization: Bearer <tk_session>` 会优先解析 `app_private.auth_sessions`,并作为用户身份来源。
- `Authorization: Bearer <supabase_access_token>` 已支持服务端验签,后端通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射到业务用户和租户成员。
- Supabase JWT 支持 `AUTH_JWT_SECRET``AUTH_JWT_JWKS_URL`;生产推荐优先配置 Supabase Auth JWKS或在自托管兼容模式下配置强随机 JWT secret。
- JWT 可以在 `app_metadata.tenant_id` 或请求租户上下文中确定当前租户;如果两者冲突,后端拒绝,不允许前端覆盖 token 中的租户声明。
- 登录后如果请求中的 `x-user-id`、query/body `userId` 与 session 用户不一致,后端返回 `AUTH_USER_MISMATCH`
- 登录后如果请求中的 `x-tenant-id` 与 session 租户不一致,后端返回 `AUTH_TENANT_MISMATCH`
- 带了无效 bearer token 的用户态接口不会回退到 `x-user-id`
@@ -37,10 +40,10 @@ Supabase 官方允许前端用 Data API 访问数据,但前提是 RLS、最小
## P0正式云端测试前必须完成
1. 正式用户鉴权
- 已支持服务端 session 解析可信 userId。
- 生产前继续接 Supabase Auth/JWT或将现有 server session 明确作为正式方案
- 已支持服务端 session 和 Supabase Auth JWT 解析可信 userId。
- 生产前必须用真实 Supabase Auth 项目或自托管 Auth 实例跑一轮云端 JWT 回归
- 前端禁止通过 query/body/header 指定 userId。
- `GET /api/auth/me` 后续要补租户成员、角色、权限返回。
- `GET /api/auth/me` 已支持 Supabase JWT后续要补租户成员、角色、权限返回。
2. 正式租户上下文
- H5 可由域名解析租户。
@@ -50,12 +53,13 @@ Supabase 官方允许前端用 Data API 访问数据,但前提是 RLS、最小
3. 平台管理员鉴权
- `x-platform-admin-key` 已可通过 `ALLOW_PLATFORM_ADMIN_KEY=false` 禁用。
- 生产前仍需替换为平台管理员 JWT/session 和审计日志
- 平台管理员也要有 JWT/session、角色、审计日志
- 已支持平台管理员 Supabase JWT且以后端 `platform_users.primary_role='platform_admin'` 为准,不只信 JWT claim
- 生产前继续补平台后台关键操作审计报表和更细权限点
4. 生产配置 fail-fast
- `NODE_ENV=production` 时禁止默认 `AUTH_CODE_PEPPER`
- 禁止默认 `AUTH_SESSION_SECRET`
- 禁止默认 `AUTH_JWT_SECRET`,除非配置了 `AUTH_JWT_JWKS_URL`
- 禁止默认 `PLATFORM_ADMIN_API_KEY`
- 禁止 `CORS_ORIGIN=*`
- 禁止 `AUTH_SMS_PROVIDER=mock`

View File

@@ -28,10 +28,10 @@
### P0 上云测试前必须补齐
1. 生产鉴权
- Supabase Auth/JWT 或服务端 session 替换迁移期 `x-tenant-id``x-user-id``x-platform-admin-key`
- 校验平台管理员、租户管理员、运营、教师、销售、代理、学生的访问边界
- 做一轮真实 JWT + RLS 回归测试
- 已补生产配置 fail-fast 和 JSON body size limit后续继续补正式身份上下文
- 已支持 Supabase Auth JWT 和迁移期 `tk_` session 双入口JWT 通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射业务身份
- 已覆盖学生、租户管理员、平台管理员、错租户、坏签名、禁用 legacy header 的 API 集成测试
- 继续补真实云端 Auth/JWKS 回归、RLS 深测、自定义角色模板和菜单/模块/字段级权限
- 前端联调时禁止继续使用 `x-user-id``x-tenant-id` 只作为租户上下文,不能作为身份依据
2. 对象存储
- 已接阿里云 OSS、腾讯云 COS、Supabase Storage 的上传/下载签名 provider。

View File

@@ -147,20 +147,34 @@ x-user-id: <userId>
生产目标:
```text
Authorization: Bearer <supabase_access_token_or_server_session>
Authorization: Bearer <supabase_access_token>
x-tenant-id: <tenantId> # 可选租户上下文;不是身份来源,必须与 JWT tenant claim 或 membership 匹配
```
当前 `apps/api` 已支持 Supabase Auth JWT 验签,并通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射到业务身份。H5/Taro 登录后可以直接把 Supabase access token 放到 `Authorization`。如果 JWT 内没有 `tenant_id` claim前端仍要根据域名/小程序码解析后的租户传 `x-tenant-id`,后端会校验该用户确实属于该租户。
生产时后端负责:
- 验证 JWT。
- 从 JWT/session 获取 userId。
- 根据 host/tenantCode/JWT claims 解析 tenant。
- 从 JWT 映射业务 userId。
- 根据 host/tenantCode/JWT claims/请求上下文解析 tenant。
- 校验用户属于该租户。
- 校验角色和权限。
- 执行业务逻辑。
前端不再传 `x-user-id`,也不能靠传 `tenantId` 获得跨租户数据。
生产 API 环境变量至少要配置:
```text
AUTH_JWT_JWKS_URL=https://<supabase-auth-host>/auth/v1/.well-known/jwks.json
# 或自托管/兼容模式下使用强随机 secret
AUTH_JWT_SECRET=<strong-jwt-secret>
AUTH_JWT_AUDIENCE=authenticated
ALLOW_LEGACY_AUTH_HEADERS=false
ALLOW_PLATFORM_ADMIN_KEY=false
```
## 对后续 AI/开发者的硬性约束
- 不要把 Supabase-first 误解成前端直写所有表。

View File

@@ -65,10 +65,27 @@ x-tenant-id: <tenantId> # 仅作为登录前/公开目录租户上下文;登
生产目标:
```text
Authorization: Bearer <supabase_access_token_or_server_session>
Authorization: Bearer <supabase_access_token>
x-tenant-id: <tenantId> # 作为租户上下文,不能作为身份依据
```
前端不应再传 `x-user-id`、query/body `userId` 来表示当前用户。后端已经实现 session 优先解析:如果 Authorization 存在,用户态接口以 session 用户为准;如果请求里伪造了不同的 `userId` 会返回 `AUTH_USER_MISMATCH`,伪造不同租户会返回 `AUTH_TENANT_MISMATCH`
前端不应再传 `x-user-id`、query/body `userId` 来表示当前用户。后端已经实现 Supabase JWT 和迁移 session 优先解析:如果 Authorization 存在,用户态接口以 token 映射出的业务用户为准;如果请求里伪造了不同的 `userId` 会返回 `AUTH_USER_MISMATCH`,伪造不同租户会返回 `AUTH_TENANT_MISMATCH``AUTH_SESSION_INVALID`
H5 使用 Supabase Auth 时,推荐请求流程:
```ts
const { data } = await supabase.auth.getSession();
const accessToken = data.session?.access_token;
await api.request('/api/profile/me', {
headers: {
Authorization: `Bearer ${accessToken}`,
'x-tenant-id': tenantStore.tenantId,
},
});
```
后端会通过 `auth.users.id -> platform_users.auth_user_id -> tenant_memberships` 映射用户身份。`x-tenant-id` 只能帮助确定当前租户上下文,不能让用户访问自己没有 membership 的租户。
生产或云端测试建议设置: