# Feishu SSO & Directory Notes

状态：draft
最后更新：2026-03-22
适用范围：`agentteam-org` Feishu v1

## 1. 目标

本文记录飞书 `SSO + 通讯录` 与 `agentteam-org` 当前设计的对齐结论，避免后续重复调查。

本文只关注 AgentTeam 当前阶段真正需要的最小能力：

- 让用户通过 Feishu SSO 或已验证邮箱/手机号进入正确的 organization
- 让 organization 能获得稳定的部门树与人员归属
- 保持 `workspace-first`、`unit -> team -> effective_grant` 不变

本文**不**试图完整覆盖飞书通讯录的全部复杂能力。

## 2. 官方来源

- 通讯录概述
  - <https://open.feishu.cn/document/server-docs/contact-v3/resources>
- 获取单个部门信息
  - <https://open.feishu.cn/document/server-docs/contact-v3/department/get>
- 获取子部门列表
  - <https://open.feishu.cn/document/server-docs/contact-v3/department/children>
- 获取部门直属用户列表
  - <https://open.feishu.cn/document/server-docs/contact-v3/user/find_by_department>
- 通过手机号或邮箱获取用户 ID
  - <https://open.feishu.cn/document/server-docs/contact-v3/user/batch_get_id>
- 用户身份概述
  - <https://open.feishu.cn/document/home/user-identity-introduction/introduction>
- 企业 SSO 方案页
  - <https://open.feishu.cn/solutions/detail/sso>

相关实现补充：

- verified mobile fallback 前置能力评估
  - `docs/vendor-profiles/feishu/mobile-auto-join-prerequisites.md`

## 3. 结论摘要

- 飞书通讯录足够支撑 `agentteam-org` 的 v1 目录同步。
- v1 只需要用到飞书的两个核心资源：
  - `department`
  - `user`
- `department` 足够映射到 `organization_unit` 的单父树。
- `user` 足够映射到 `organization_member`。
- “按部门授权”仍不直接把 unit 暴露给 core，而是继续通过 `unit_backed team`。
- Feishu 同时具备认证和目录能力，但在 `agentteam-org` 内仍应拆成两条逻辑：
  - `auth_provider = feishu`
  - `directory_provider = feishu`

## 4. 和 AgentTeam 的映射

Feishu 对象到 AgentTeam 模型的推荐映射：

| Feishu | AgentTeam | 说明 |
|---|---|---|
| `department` | `organization_unit` | org chart 主载体 |
| `parent_department_id` | `organization_unit.parent_unit_id` | 单父树映射 |
| `user` | `organization_member` | 人类目录身份与组织成员事实源 |
| department user relation | `organization_member_unit_membership` | 主属/附属归属 |
| department-based access | `unit_backed team` | 保持当前授权链 |

对当前架构的含义：

- `core` 不需要理解飞书目录图。
- `agentteam-org` 负责把飞书目录先落成本地投影，再重新编译成 `effective_grant`。
- Feishu 接入不会改变 `workspace` 是真实协作边界这一点。

## 5. 飞书目录对象里 v1 真正需要的字段

### 5.1 Department

从官方部门接口看，v1 至少可稳定使用：

- `open_department_id`
  - 系统生成，租户内全局唯一
- `department_id`
  - 可自定义，存在复用和治理复杂度，不建议作为 v1 主键映射
- `parent_department_id`
  - 父部门；根部门为 `0`
- `name` / `i18n_name`
- `leader_user_id`
- `order`
- `status.is_deleted`
- `member_count` / `primary_member_count`

AgentTeam v1 建议：

- 外部目录主键优先使用 `open_department_id`
- `organization_unit.external_provider = feishu`
- `organization_unit.external_id = open_department_id`
- `parent_department_id = 0` 视作根节点

### 5.2 User

从“获取部门直属用户列表”与“通过手机号或邮箱获取用户 ID”接口看，v1 至少可稳定使用：

- `user_id`
  - 租户内稳定身份，跨同租户应用一致
- `open_id`
  - 应用内身份，不适合作为跨应用/跨绑定的主目录键
- `union_id`
  - 同一开发商下多应用统一身份
- `name`
- `email`
- `mobile`
- `status`
  - `is_frozen`
  - `is_resigned`
  - `is_activated`
  - `is_exited`
  - `is_unjoin`
- `department_ids`
- `orders`
  - 含 `is_primary_dept`

AgentTeam v1 建议：

- 目录主键优先使用 `user_id`
- `organization_member.external_provider = feishu`
- `organization_member.external_id = user_id`

认证绑定建议单独处理：

- `open_id` / `union_id` / SSO subject 不应直接覆盖目录侧 `external_id`
- auth-binding 与 directory-binding 应分离存储

## 6. 飞书 ID 选择结论

根据飞书官方“用户身份概述”：

- `user_id`
  - 租户内用户唯一标识
  - 同租户内跨应用稳定
- `open_id`
  - 应用内用户唯一标识
  - 不适合跨应用通信
- `union_id`
  - 同一应用服务商下多应用统一身份

对 `agentteam-org` 的直接结论：

- 目录同步主键优先用 `user_id`
- 应用内登录态或应用绑定可保留 `open_id`
- 若未来出现多应用/多 Feishu App 绑定需求，可考虑 `union_id` 作为 auth-binding 辅助键

## 7. v1 推荐调用路径

### 7.1 目录全量导入

推荐使用 `tenant_access_token` 作为 worker 访问凭证。

原因：

- v1 目标是后台 provider 同步，不是以某个 end-user 的可见范围驱动目录图
- 官方部门与用户接口都支持应用身份
- 用户身份调用会受“组织架构可见范围”影响，不适合作为稳定的后台同步方案

推荐流程：

1. 配置一个或多个 root department
2. 用 `department/get` 校验 root department 存在
3. 用 `department/children` 拉子部门树
4. 递归写入本地 `organization_unit`
5. 对每个 department 用 `user/find_by_department` 拉直属用户
6. 写入 `organization_member`
7. 写入 `organization_member_unit_membership`
8. 按需要触发 `unit_backed team` membership reconcile
9. 重新编译 `effective_grant`

### 7.2 登录后自动加入 organization

Feishu v1 允许两类 bootstrap：

- Feishu SSO 成功后，基于 auth-binding 命中唯一 directory user
- 用户使用已验证邮箱/手机号登录后，通过 `user/batch_get_id` 查找飞书用户

需要注意：

- `batch_get_id` 支持邮箱和手机号，但明确说明：
  - 不支持企业邮箱
  - 最多各 50 条
  - 可选 `include_resigned`
- 自动加入必须要求唯一命中
- 命中后只做 member bind，不直接放大 workspace 权限

当前实现状态（`2026-03-23`）：

- Feishu SSO 已完成
- verified email fallback auto-join 已完成
- verified mobile fallback 已完成

当前 mobile path 已补齐为：

- `users.mobile` / `users.mobile_verified_at`
- authenticated user mobile verification flow
- 兼容旧 `{ email, password }` wire contract 的 verified mobile login
- `FindUserByMobile` / `FindUserByLoginIdentifier`
- Feishu `user/batch_get_id.mobiles` 唯一命中 auto-join

实现细节见 `docs/vendor-profiles/feishu/mobile-auto-join-prerequisites.md`。

## 8. v1 需要的飞书权限与范围

从官方文档可见，v1 至少会碰到这些权限面：

- 获取通讯录基本信息
- 获取部门组织架构信息
- 获取用户邮箱信息
- 获取用户手机号
- 获取用户 user ID
- 通过手机号或邮箱获取用户 ID

实现含义：

- 必须显式记录“当前应用是否拿到了这些权限”
- 必须处理“通讯录范围不足”导致的部分目录不可见
- root department = `0` 时，会额外要求更大的可见范围

## 9. v1 明确不做的事

以下能力先不进入 Feishu v1：

- 用户组、角色、单位、职级、序列、职务、自定义字段的完整同步
- 复杂雇佣关系、历史关系、全部 HR 属性
- 用用户可见范围驱动 provider 后台同步
- 登录时全量抓整棵通讯录
- 因 auto-join 自动开放全部 workspace

## 10. 对实现的直接影响

### 10.1 新增 provider 配置

至少需要：

- Feishu app / tenant 绑定
- `auth_provider = feishu`
- `directory_provider = feishu`
- root department 列表

### 10.2 新增绑定模型

至少需要：

- auth-binding
  - 存 Feishu SSO subject / open_id / union_id 一类认证键
- directory-binding
  - 继续存 `organization_*` 上的 `external_provider/external_id`

### 10.3 新增 worker 能力

至少需要：

- initial import
- rerun / reconcile
- 分页继续
- scope / permission 错误可解释

### 10.4 新增 auto-join 门禁

至少需要：

- 只接受已验证 email/mobile
- 唯一命中才自动加入
- 多 organization 命中时转人工/显式选择
- 不因 auto-join 自动下发 workspace 全量权限

### 10.5 verified mobile fallback 的当前实现

当前仓库已经满足 Feishu SSO、verified email fallback 与 verified mobile fallback。

当前 mobile path 的最小实现包括：

- `users.mobile` `UNIQUE NULL` 与 `users.mobile_verified_at`
- authenticated user 发起 / 完成 mobile verification 的通用 auth API
- 仍复用旧 `{ email, password }` login wire contract，但该 `email` 字段现在可承载 verified mobile identifier
- store 层新增 `FindUserByMobile` / `FindUserByLoginIdentifier`
- provider-local `BatchGetUserIDsByMobile` + 唯一命中 / 歧义拒绝 / placeholder rebind contract

结论：Feishu `batch_get_id.mobiles` 仍然只留在 `agentteam-org` provider 内部；通用 mobile identity / verification / login 能力则继续留在 core/app auth。

## 11. 仍待确认的问题

- 实际落地时选择哪一种 Feishu SSO 形态
  - 扫码登录
  - OIDC / OAuth 风格
  - 企业已有 SSO 系统与飞书身份集成后的回跳
- auth-binding 的最终表设计
- 多个 Feishu tenant / 多 organization 的绑定边界
- 是否需要在 v1 就支持事件驱动增量同步，还是先用手动/定时全量同步

## 12. 建议的实现顺序

1. 先完成 sync provenance 基础骨架
2. 再做 Feishu auth-binding / directory-binding 模型
3. 再做 root department 配置与目录导入 worker
4. 再做 Feishu SSO + verified email auto-join
5. AgentTeam 通用 mobile identity / verification / login surface（completed）
6. verified mobile fallback auto-join（completed）
7. explainability、管理 UI、E2E
