# Base44 访问申请 Node Facade 实施交接

## 1. 文档状态

- 适用项目：`node_facade_base44_supabase`。
- 适用链路：Base44 Web → Base44 Backend Function → Node Facade → Go → Supabase V3。
- 上游状态：Supabase V3 访问申请 migration、repair、verify 和 schema cache reload 已完成。
- Go 状态：访问状态、创建申请、Session exchange 和管理员审核路由已经实现。
- Node 状态：三个用户端方法、Backend Function assertion 注入、保护字段、幂等和安全错误字段已在本地实现并通过 mock 测试。
- 本阶段范围：完成真实 Base44 Function、Go 联调和发布验证；通过后再修改 Base44 页面。
- 非本阶段范围：`react_ts_mysql_role`、MySQL V2.1、Delphi/HttpSql 七个 Fun。

本文最初是 Node 工程实施的冻结交接契约，现同时记录已落地实现和剩余验收边界。当前本地实现不等于真实 Base44 Runtime、Go、Supabase 链路已经联调通过；完成配置、真实集成测试和发布顺序验证后，才能进入 Base44 页面开发。

## 2. 已确认的当前事实

### 2.1 Go 已提供的用户端接口

| 方法 | 路径 | Go 鉴权 | 请求体 |
| --- | --- | --- | --- |
| POST | `/ts/dbbase/auth/base44/access-status` | 短时 Base44 assertion | 只允许 `assertion` |
| POST | `/ts/dbbase/auth/base44/access-requests` | 短时 Base44 assertion | 只允许 `assertion`、`request_message` |
| POST | `/ts/dbbase/auth/base44/exchange` | 短时 Base44 assertion | 只允许 `assertion` |

三个接口都不使用 Go Session Bearer Token。这里的“公开 Auth 路径”只表示 Go Bearer Token 非必需，不表示 Base44 Backend Function 可以跳过 Base44 用户认证。

Go 路由依据：

- `go_qoder_file/main.go`
- `go_qoder_file/src/handlers/access_request_http.go`
- `go_qoder_file/src/handlers/supabase_auth_base44.go`

### 2.2 历史缺口与当前实现

下列项目曾是 Node Facade 的缺口，当前均已完成本地实现：

1. `src/auth_service.js` 已提供访问状态、创建申请和当前用户 exchange。
2. `src/service_paths.js` 已集中三个精确路径，并把创建申请纳入幂等判断。
3. `src/supabase_function_handler.js` 已通过 core `ResolveBase44User` 获取可信用户、签发 assertion 并重建 Body。
4. `src/base44_request.js` 已集中页面字段白名单、受保护字段和申请说明校验。
5. `src/base44_assertion.js` 已实现 RS256、唯一 JTI 和最长 60 秒的失败关闭配置。
6. `SupabaseFacadeError` 已白名单保留 `code/status/request_id/retry_allowed_at`。

仍未完成的是：真实 Base44 Runtime 与 Go 的端到端联调、core/facade 正式版本发布、registry 固定版本安装验证和 Base44 页面改造。

## 3. 冻结目标架构

```text
Base44 Web
  │  只提交页面动作、申请说明和幂等键
  ▼
Base44 Backend Function
  │  使用 Base44 服务端 SDK 验证当前用户
  ▼
node_facade_base44_supabase
  │  读取可信 User ID/email/disabled/App ID
  │  为本次调用签发唯一 JTI、最长 60 秒的 assertion
  │  将 assertion 注入固定 Go 请求体
  ▼
go_qoder_file
  │  验签、映射 App、生成内部摘要、限流、幂等、调用固定 RPC
  ▼
Supabase V3
```

冻结原则：

- Base44 页面不得生成或填写 `sub/email/email_verified/disabled/provider_app_id`。
- Base44 页面不得持有 assertion 私钥、Supabase Secret 或 `service_role` Key。
- Node Facade 不直连 Supabase，不调用任意 PostgREST RPC。
- Node Facade 不生成 Go 内部的 JTI 摘要、主体摘要、Bucket 摘要、幂等摘要或请求指纹。
- Node Facade 不接受浏览器提供的 App 内部 ID、限流阈值或内部哈希。
- Go 继续作为 App 映射、业务准入、幂等、限流和 Session 创建的唯一服务端入口。

## 4. Node Facade 必须完成的功能

### 4.1 Base44 服务端身份解析

继续使用 `CreateBase44ClientFromRequest(Request)` 和 Base44 服务端 SDK 的 `auth.me()` 验证用户。禁止从请求 Body、Query 或自定义浏览器 Header 读取可信身份。

Node 内部应归一化为以下只读对象：

```js
{
  ProviderAppId: 'base44-app-id',
  Subject: 'stable-base44-user-id',
  Email: 'verified@example.com',
  EmailVerified: true,
  Disabled: false,
}
```

要求：

- `Subject` 必须来自 Base44 稳定用户 ID，trim 后非空。
- `Email` 必须来自 Base44 服务端用户对象，规范化为 trim 后小写邮箱。
- `EmailVerified=true` 只能来自 Base44 服务端可信保证或明确的服务端适配器契约，不能接受浏览器布尔值。
- `Disabled` 必须解析为真正的 Boolean；无法取得明确状态时应失败关闭，不能把任意字符串当作 `false`。
- `ProviderAppId` 必须来自 Backend Function 的服务端环境或受控部署上下文，不能由页面选择。
- 缺失或无效身份统一在 Facade 层失败，不能签发 assertion。

### 4.2 短时 assertion 签发

当前实现模块：

```text
src/base44_assertion.js
```

签发 JWT Header：

```json
{
  "alg": "RS256",
  "kid": "base44-key-2026-01",
  "typ": "JWT"
}
```

签发 Claims：

```json
{
  "iss": "configured-base44-issuer",
  "aud": "configured-go-audience",
  "provider_app_id": "base44-app-id",
  "sub": "stable-base44-user-id",
  "email": "verified@example.com",
  "email_verified": true,
  "disabled": false,
  "iat": 1760000000,
  "exp": 1760000045,
  "jti": "new-random-value-for-this-call"
}
```

固定要求：

- 第一版使用 `RS256`；禁止 `none` 和 `HS256`。
- 每次调用三个 Go 接口之一，都必须生成全新的高熵 JTI。
- `exp > iat`，且 `exp - iat <= 60` 秒；建议实际签发 45 秒。
- JWT Header 必须包含非空 `kid`。
- 不设置 `jku/x5u/jwk`。
- 私钥只保存在 Base44 Backend Function Secret 中。
- 日志、错误、响应和监控不得输出原始 assertion、私钥或 JTI。
- 使用经过验证且兼容实际 Base44 Runtime 的 JOSE 实现；禁止自行拼装不完整的 JWT 验签/签名协议。

Node 配置必须与 Go 配置对应：

| Node Secret/配置 | Go 配置 |
| --- | --- |
| assertion algorithm | `base44AssertionAlgorithms` |
| assertion issuer | `base44AssertionIssuers` 中的一项 |
| assertion audience | `base44AssertionAudiences` 中的一项 |
| assertion `kid` + 私钥 | `base44AssertionPublicKeys[kid]` 对应公钥 |
| assertion lifetime | 不超过 `base44AssertionMaxLifetimeSeconds` |

`base44AssertionReplayPepper` 和访问申请 HMAC pepper 只属于 Go，不能复制到 Node 或 Base44 页面。

### 4.3 在 Backend Function 内注入 assertion

冻结页面边界是：页面调用 Facade 的业务方法，Backend Function 在验证 Base44 用户后签发并立即转发 assertion，不把 assertion 返回给浏览器。

Backend Function 转发给 Go 的最终 Body 必须是：

```text
access-status  -> { assertion }
access-request -> { assertion, request_message }
exchange       -> { assertion }
```

页面即使提交了同名 `assertion`、`sub`、`email`、`disabled`、`provider_app_id` 或 `app_id`，Backend Function 也不得信任或透传。建议直接拒绝包含这些受保护字段的页面请求，而不是静默采用。

当前采用的实现是：core 公开通用 `ResolveBase44User`，本 Facade 为三个固定 Base44 路径执行身份解析和 Body 重建。没有向 core 增加任意 Body 转换钩子，因此 Go/Supabase 请求语义不会进入通用层；同一次请求只解析一次可信用户，也不会向页面暴露任意 Body 转换能力。

如果修改 `node_lib_base44_core`，必须先发布兼容版本，再更新本项目依赖和 lockfile；不得只修改本机相邻目录而不更新包版本。

## 5. 冻结的 Facade 客户端 API

### 5.1 查询访问状态

建议方法：

```js
Client.AuthApi.GetBase44AccessStatus()
```

发送到 Base44 Backend Function 的业务 Body 应为空对象；由 Backend Function 注入 assertion。

最终 Go 请求：

```http
POST /ts/dbbase/auth/base44/access-status
Content-Type: application/json

{"assertion":"server-signed-jwt"}
```

不发送 Go `Authorization` 和 `Idempotency-Key`。

### 5.2 创建访问申请

建议方法：

```js
Client.AuthApi.CreateBase44AccessRequest(
  { RequestMessage: '申请访问业务后台' },
  { IdempotencyKey: 'stable-key-for-this-logical-submit' },
)
```

Facade 将 PascalCase 客户端输入转换为 Go Body：

```json
{
  "request_message": "申请访问业务后台"
}
```

Backend Function 再注入 assertion，最终请求只包含：

```json
{
  "assertion": "server-signed-jwt",
  "request_message": "申请访问业务后台"
}
```

要求：

- `RequestMessage` 可空；存在时 trim，长度上限与 Go 当前契约保持一致。
- 必须在 HTTP Header 中发送 `Idempotency-Key`。
- 相同逻辑提交的网络重试复用同一个幂等键。
- 每次重试仍必须签发新的 assertion 和 JTI，不能复用旧 assertion。
- 新的一次用户提交生成新的幂等键。

### 5.3 Session exchange

建议增加面向页面的安全方法：

```js
Client.AuthApi.ExchangeCurrentBase44User()
```

最终 Go 请求：

```http
POST /ts/dbbase/auth/base44/exchange
Content-Type: application/json

{"assertion":"server-signed-jwt"}
```

不发送 Go `Authorization` 和 `Idempotency-Key`。Go 成功返回的 Session Token 可以按当前应用会话方案交给页面保存，但 assertion 本身不得返回页面。

当前 `ExchangeBase44(Assertion)` 可在兼容期保留并标记 deprecated，但新的 Base44 页面禁止使用。Backend Function 对三个固定路径仍必须用服务端新 assertion 重建 Go Body，不能信任兼容方法传入的 assertion。

## 6. 路径与幂等规则修改

`src/service_paths.js` 至少需要：

1. 将下列路径加入“无需 Go Bearer Token”的精确集合：

```text
/ts/dbbase/auth/base44/access-status
/ts/dbbase/auth/base44/access-requests
/ts/dbbase/auth/base44/exchange
```

2. `RequiresIdempotency(Path, Method)` 对以下组合返回 `true`：

```text
POST /ts/dbbase/auth/base44/access-requests
```

3. access-status 和 exchange 不添加幂等 Header。
4. 继续使用精确路径判断，禁止使用宽泛的 `/auth/base44/*` 绕过 Go Bearer Token。

`src/facade_request.js` 和 `src/idempotency_key.js` 应继续负责创建或读取幂等键，但创建申请的客户端调用必须在第一次提交前取得 key，确保页面侧网络重试可以复用。

## 7. 响应和错误契约

Go 正式成功响应仍为：

```json
{
  "result": 0,
  "ret_str": {},
  "request_id": "request-id"
}
```

Facade 业务方法继续返回 `ret_str`，不得把数据库 RPC 内部对象直接暴露给页面。

Go 非 2xx 错误必须转换为 `SupabaseFacadeError`，至少保留：

```text
Code
StatusCode
RequestId
Message
RetryAllowedAt（Go 返回时）
```

当前代理错误链可能丢失 `retry_allowed_at`。应扩展为“允许字段白名单传递”，不得透传任意上游错误对象、SQLSTATE、SQL 文本或内部堆栈。

页面需要识别的主要错误：

| code | 页面含义 |
| --- | --- |
| `AUTH_EXTERNAL_TOKEN_INVALID` | assertion 无效、过期或 JTI 已消费；重新发起完整 Facade 调用 |
| `AUTH_EXTERNAL_ACCOUNT_DISABLED` | Base44 账号已停用 |
| `ACCESS_REQUEST_DISABLED` | 当前账号不能申请 |
| `ACCESS_REQUEST_FEATURE_DISABLED` | 当前 App 未开放申请 |
| `ACCESS_ALREADY_GRANTED` | 已存在访问资格，应重新查询状态或 exchange |
| `ACCESS_REQUEST_ALREADY_PENDING` | 已有待审核申请 |
| `ACCESS_REQUEST_COOLDOWN` | 冷却期未结束，读取 `RetryAllowedAt` |
| `REQUEST_RATE_LIMITED` | 提交频率过高 |
| `IDEMPOTENCY_KEY_CONFLICT` | 同一 key 对应不同申请内容，禁止自动覆盖 |
| `IDEMPOTENCY_IN_PROGRESS` | 相同申请处理中，短暂等待后使用新 assertion、原幂等键重试 |
| `STAFF_CONFLICT` | Staff 状态冲突，需要管理员处理 |
| `AUTH_EXTERNAL_IDENTITY_CONFLICT` | Base44 身份冲突，需要管理员处理 |
| `AUTH_EXTERNAL_PROVIDER_UNAVAILABLE` | assertion 配置或外部身份服务不可用 |
| `AUTH_SUPABASE_UNAVAILABLE` | Go 下游数据库暂时不可用 |

Node 不应把这些业务错误统一改写为一个 500/503，也不能把 HTTP 非 2xx 当成无结构字符串。

## 8. 配置清单

Base44 Backend Function 至少需要下列 Secret/配置；实际名称可按部署平台规范调整，但必须在文档中给出最终名称：

```text
TS_GO_API_BASE_URL
BASE44_ASSERTION_ALGORITHM=RS256
BASE44_ASSERTION_KEY_ID
BASE44_ASSERTION_PRIVATE_KEY_PEM
BASE44_ASSERTION_ISSUER
BASE44_ASSERTION_AUDIENCE
BASE44_PROVIDER_APP_ID
BASE44_ASSERTION_LIFETIME_SECONDS=45
```

校验要求：

- 启动或首次调用时检查所有必填配置。
- lifetime 必须在 `1..60` 范围内。
- 私钥必须能按配置算法解析，并与 Go 中 `kid` 对应公钥匹配。
- issuer、audience、provider app id 均 trim 后非空。
- 生产环境不得使用示例私钥。
- 配置错误返回稳定服务端错误，不能回退为未签名、HS256 或浏览器提供字段。

本项目仍然不需要：

```text
Supabase URL
Supabase service_role/secret key
Go 的 Base44 assertion replay pepper
Go 的 Auth HMAC pepper
数据库连接串
```

## 9. 安全边界

必须满足：

- Base44 Backend Function 的 `RequireAuth` 保持开启。
- 不启用 `ForwardUserHeaders` 将 Base44 ID/email 直接作为 Go 信任来源。
- Go 只信任签名 assertion，不信任 `X-Base44-User-Id` 或 `X-Base44-User-Email`。
- 页面不能选择内部 `app_id`，只能由 assertion 的 `provider_app_id` 在 Go 中映射。
- 页面不能提交 `email_verified` 或 `disabled`。
- 页面不能提交 `window_seconds/request_limit/block_seconds`。
- 页面不能提交 `external_subject_hash/bucket_key_hash/idempotency_key_hash/request_fingerprint/jti_hash`。
- Node 不缓存 assertion；每次请求即时签发。
- 三个接口和其响应都设置 `Cache-Control: no-store`。
- CORS/Origin 继续限制为实际 Base44 应用来源。
- 日志只记录操作名、HTTP 状态、Go `request_id`、安全错误码和耗时。

禁止记录：

```text
原始 assertion
原始 JTI
签名私钥
完整 Base44 用户对象
Go Session Token
Supabase Secret/service_role
内部 HMAC 摘要
完整上游错误对象或堆栈
```

## 10. 已完成源码检查清单

当前本地源码已完成：

1. `src/auth_service.js`
   - 已新增 `GetBase44AccessStatus`、`CreateBase44AccessRequest` 和 `ExchangeCurrentBase44User`。
   - 旧 `ExchangeBase44(Assertion)` 已保留兼容，新页面不得使用。
2. `src/service_paths.js`
   - 已集中三个精确公开 Auth 路径和创建申请幂等判断。
3. `src/supabase_function_handler.js`
   - 已对三个固定路径执行服务端身份解析、assertion 签发和 Body 重建。
   - 已禁止页面覆盖受保护字段。
4. 已新增 `base44_assertion.js` 和 `base44_request.js`；身份登录态解析复用 core。
5. `src/facade_error.js`
   - 已保留允许公开的 `RetryAllowedAt`。
6. `src/messages.js`
   - 已增加稳定的 assertion 配置、身份缺失和签发失败文案。
7. `package.json`
   - 已把新增源码加入 `npm run check`。
   - 已锁定 JOSE 兼容版本并更新 lockfile。
8. `test/supabase.test.js`
   - 已增加本交接第 11 节的本地 assertion、Handler、幂等和错误测试。
9. 文档
   - 已同步 `README.md`、usage、API、composition、project status、AI 指南和日志。

`node_lib_base44_core` 只新增通用且向后兼容的 `ResolveBase44User`，不感知 Go/Supabase/Base44 访问申请领域。正式发布仍须遵守 core 先于 facade 的顺序。

## 11. 测试状态与真实联调清单

第 11.1 至 11.3 节已由本地 mock 覆盖；第 11.4 节必须连接真实 Base44 Function、Go 和 Supabase 后执行，不能用 mock 结果代替。

### 11.1 assertion 单元测试

- Header 包含预期 `alg/kid/typ`。
- Claims 包含全部必填字段。
- 使用配对公钥可以验证签名。
- `exp - iat <= 60`。
- 连续签发的 JTI 不同。
- 邮箱已规范化。
- 缺失 User ID、邮箱、可信 email verified、disabled Boolean 或 provider app id 时拒绝签发。
- 私钥、算法、issuer、audience、kid 配置无效时失败关闭。

### 11.2 Handler 安全测试

- 三个路径都要求有效 Base44 用户。
- 三个路径不转发 Go Authorization。
- 浏览器提交伪造的 `sub/email/disabled/provider_app_id/app_id/assertion` 被拒绝或由服务端安全重建，绝不成为 Go 的可信值。
- access-status 最终 Go Body 只有 assertion。
- access-requests 最终 Go Body 只有 assertion 和 request_message。
- exchange 最终 Go Body 只有 assertion。
- 三次调用分别使用三个 JTI；同一次调用不会重复签发或重用。
- 代理响应和日志中不出现 assertion/JTI/私钥。

### 11.3 幂等和错误测试

- 创建申请自动携带 `Idempotency-Key`。
- access-status/exchange 不携带幂等 Header。
- 同一次逻辑重试可传入相同 key，但使用新的 assertion。
- `code/status/request_id/retry_allowed_at` 能安全传到 `SupabaseFacadeError`。
- 未知上游字段、SQL 错误和堆栈不会传到页面。

### 11.4 真实 Go 联调

- 未授权用户查询得到 `application_available`。
- 创建申请成功并返回 pending 申请。
- 相同 key、相同内容返回第一次结果。
- 相同 key、不同内容返回 `IDEMPOTENCY_KEY_CONFLICT`。
- pending 用户查询得到 `pending_review`。
- 管理员批准后，用户用新 assertion exchange 成功。
- disabled 用户同步后被拒绝。
- 过期或重复 JTI 返回 `AUTH_EXTERNAL_TOKEN_INVALID`。
- 冷却和限流错误保留恢复时间/错误码。

## 12. 本轮不由 Node Facade 完成的功能

以下内容不应混入本轮 Node 用户端改造：

- 修改 Supabase migration、RPC、表、RLS 或数据库权限。
- 修改 Go 的 App 映射、内部哈希、限流或幂等实现。
- 修改 MySQL V2.1 或 Delphi/HttpSql 七个 Fun。
- 在 Node 中直接调用 Supabase PostgREST。
- 在申请阶段创建 Staff、Go User、external identity 或 Session。
- 实现 Base44 身份自动合并。

管理员访问申请列表、详情、批准和拒绝已经属于 Go Admin API 和 `react_ts_role` 管理页面的后续工作。普通 Base44 用户页面只需要访问状态、创建申请和 exchange 三个流程。

## 13. Node 完成交付物

Node 工程完成后必须提供：

1. 修改后的真实文件清单。
2. 三个 Facade 客户端方法的最终名称和签名。
3. Base44 Backend Function 的真实装配示例。
4. Secret/配置名称清单，不包含真实值。
5. assertion Header/Claims 脱敏示例。
6. 三个最终 Go 请求示例，assertion 使用占位符。
7. 单元测试、Handler 测试和真实 Go 联调结果。
8. `npm run check`、`npm test` 结果。
9. 如修改 core，提供 core 版本、兼容性测试和发布顺序。
10. 与本文不一致或尚未实现的项目清单。

只有这些材料通过后，Base44 页面才能按已冻结的 Facade API 开发。

## 14. 推荐实施顺序

1. 冻结 Node/Go 配置对应关系并生成签名密钥对。
2. 在 Node Facade 实现可信身份适配和 assertion 签发。
3. 实现 Backend Function 的固定 Body 重建。
4. 增加三个 AuthApi 方法和精确路径规则。
5. 补齐创建申请幂等 Header。
6. 修复允许字段错误传递。
7. 完成单元测试和代理测试。
8. 与真实 Go 三个用户端接口联调。
9. 发布 `node_lib_base44_core`（如有改动）。
10. 发布 `node_facade_base44_supabase`。
11. 最后修改 Base44 页面。

## 15. 关联依据

- [Base44 访问申请冻结需求](../../../../VUE/react_ts_role/docs/fun/account/ACCESS_REQUEST_REQUIREMENTS.md)
- [当前 Facade 组合流程](composition.md)
- [当前 Facade API](../api/supabase.md)
- [当前 Facade 使用说明](../usage.md)
- [Go 访问申请 Handler](../../../../go/go_qoder_file/src/handlers/access_request_http.go)
- [Go Base44 exchange Handler](../../../../go/go_qoder_file/src/handlers/supabase_auth_base44.go)
- [Go Base44 assertion 验证](../../../../go/go_qoder_file/src/supabaseauth/base44.go)
