实现企微首次免密自动绑定(07-28-all-account-login-readiness)
这个提交包含在:
@@ -320,6 +320,97 @@ one-time onboard -> explicit private consent -> safe unique employee-only write
|
||||
business role assignment -> separate administrator-audited operation
|
||||
```
|
||||
|
||||
## Scenario: Passwordless First WeCom Login For Existing OA Accounts
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: changing ordinary `mode=login`, identity binding, directory-email
|
||||
matching, the password login fallback or account-activation readiness.
|
||||
- Purpose: every existing active OA employee must be able to enter from the
|
||||
WeCom workbench on the first attempt without entering an OA password, while
|
||||
ambiguous identities remain fail-closed.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Entry: `GET /api/auth/wecom/login?returnTo=<internal-path>` with
|
||||
`snsapi_base`.
|
||||
- OAuth callback identity: internal `userid` from `/cgi-bin/auth/getuserinfo`.
|
||||
- Unbound member profile: `GET /cgi-bin/user/get?userid=<oauth-userid>` using
|
||||
the in-memory app access token.
|
||||
- Binding DB: unique `WecomIdentityBinding.employee_id` and
|
||||
`WecomIdentityBinding.external_user_id`.
|
||||
- Audit actions: `AUTH_WECOM_AUTO_BOUND` followed by
|
||||
`AUTH_WECOM_LOGIN_SUCCESS`; the latter records only `bindingSource`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Existing active binding is the fast path and does not call `/user/get`.
|
||||
- Without a binding, exact OA `employee_no == userid` is a valid stable match.
|
||||
A normalized WeCom `biz_mail`/`email` may also match one existing active OA
|
||||
`Employee.email` using case-insensitive exact equality. If userid and email
|
||||
point to different employees, stop.
|
||||
- Transactionally recheck both unique binding directions before create. A
|
||||
concurrent unique-constraint race is a conflict, not permission to overwrite.
|
||||
- Ordinary login never creates an employee, updates roles, writes the WeCom
|
||||
directory, matches by name/phone, or consumes a pending cookie during OA
|
||||
password login.
|
||||
- Access tokens, full userid, enterprise email and raw provider payload stay in
|
||||
request memory. Auto-bind audit contains only `matchBy` and
|
||||
`emailMatched`; errors and redirects use stable categories.
|
||||
- Missing enterprise email does not block an exact userid/employee-number
|
||||
match. Without either stable match, no session or binding is created.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Existing binding + active employee -> direct session.
|
||||
- Existing binding + inactive employee -> `account_mapping_inactive`.
|
||||
- `/user/get` network/API failure or no OA match ->
|
||||
`account_mapping_required`; no write.
|
||||
- WeCom member inactive or matched OA employee inactive ->
|
||||
`account_mapping_inactive`; no write.
|
||||
- Userid/email point to different employees, either side already has a
|
||||
different binding, identity mismatch or binding race ->
|
||||
`account_mapping_conflict`; no overwrite.
|
||||
- Password login with a legacy `wecom_login_bind` cookie -> normal password
|
||||
login only, clear the cookie, return `wecomBindingStatus=null`.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: an unbound active member's enterprise email matches exactly one active
|
||||
OA employee; callback creates one binding and audit, then redirects into the
|
||||
requested OA page without a password.
|
||||
- Base: a previously bound member uses the existing fast path and creates only
|
||||
the normal WeCom login session/audit.
|
||||
- Bad: bind by display name, overwrite another userid, create a new employee,
|
||||
or ask the member to enter an OA password to complete first binding.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Service tests: profile userid equality, member state, business-email
|
||||
normalization, userid-only match, email-only match, split-key conflict,
|
||||
inactive employee, cross-binding and redacted audit.
|
||||
- Route tests: existing binding fast path, passwordless auto-bind + session,
|
||||
provider-unavailable fail-closed redirect and no pending binding cookie.
|
||||
- Password-route regression: a legacy pending cookie cannot create/update a
|
||||
binding and is cleared.
|
||||
- Frontend typecheck/build and focused WeCom notice tests; full backend suite,
|
||||
Prisma validation, sensitive-content and Trellis governance gates.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```text
|
||||
unbound OAuth userid -> password login -> consume pending cookie -> bind
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```text
|
||||
OAuth userid -> existing binding OR exact userid/unique enterprise email match
|
||||
-> transactional binding -> redacted audit -> direct WeCom session
|
||||
```
|
||||
|
||||
## Scenario: WeCom Directory Export With Optional Phone
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,81 @@
|
||||
# 全员登录可用性设计
|
||||
|
||||
## 1. Boundary
|
||||
|
||||
本任务在现有企微 OAuth 回调内补充确定性自动绑定,不新增公开认证端点、不读取或记录任何真实密码。普通企微登录只绑定既有 active OA 账户,不创建员工、不修改角色、不写企微通讯录。
|
||||
|
||||
## 1.1 WeCom Direct-login Contract
|
||||
|
||||
```text
|
||||
企微 OAuth callback 得到内部 userid
|
||||
-> 已有唯一 binding:直接创建会话
|
||||
-> 无 binding:读取该 userid 的企微通讯录资料
|
||||
-> 企业邮箱精确匹配唯一 active OA 账户
|
||||
-> 目标账户无 binding:事务内 upsert binding + 脱敏审计 + 创建会话
|
||||
-> 任何缺失/无匹配/inactive/冲突/API 失败:失败关闭
|
||||
```
|
||||
|
||||
匹配键优先级固定为已有 userid binding,其次为企微返回的规范化企业邮箱。姓名不得参与自动决策;手机号不作为本轮普通登录兜底,避免号码复用或格式漂移扩大身份边界。
|
||||
|
||||
## 2. Login Baseline
|
||||
|
||||
每个 active 账户必须同时满足:
|
||||
|
||||
1. `WeCom SSO ready`:属于当前企微通讯录的员工存在唯一 binding,并由本人完成一次工作台直接登录;首次登录不需要 OA 密码。
|
||||
2. `password fallback ready`:员工本人已设置并掌握密码;它是企微异常时的独立回退,不是首次 binding 的前置条件。
|
||||
|
||||
inactive/archived 账户不进入本轮登录覆盖率分母。
|
||||
|
||||
## 3. Administrator Recovery
|
||||
|
||||
```text
|
||||
受控终端 -> 精确定位唯一 active 管理员 -> 静默设置一次性密码
|
||||
-> 清锁定、撤旧会话、递增 token version、失效旧验证码并留脱敏审计
|
||||
电脑浏览器 -> 使用一次性密码登录 -> 立即设置本人新密码
|
||||
手机企微工作台 -> 重新进入并完成 OAuth userid 校验
|
||||
-> 新版 OAuth 回调用 userid 或企微企业邮箱精确匹配既有 active OA 账户
|
||||
-> 自动建立唯一 binding 并直接创建会话
|
||||
-> 再次从工作台进入并验证免登
|
||||
```
|
||||
|
||||
一次性密码只允许由用户在受控终端静默输入,或写入仓库外权限为 `0600` 的临时文件供用户本机读取一次;不得出现在聊天、argv、环境变量、日志或仓库。它只用于管理员账号恢复,不参与企微首次 binding。
|
||||
|
||||
若自动匹配缺失或冲突,停止操作并只读核对 userid、企业邮箱和 OA active 账户的唯一映射;不得按姓名猜测、不得用密码登录覆盖 binding。个人设置主动绑定只保留为管理员核实后的显式维护入口。
|
||||
|
||||
## 4. Remaining Active Accounts
|
||||
|
||||
其余 active 账户已有 binding,但尚无真实登录证据。每名员工先直接从企微工作台进入,验证 binding 和免登是否可用;企微免登通过后再按优先级安排密码回退,不让密码流程阻塞主入口验收。管理员只记录 `password_login_pass`、`wecom_login_pass`、`blocked_reason` 三类脱敏结果,不收集密码、验证码或完整账号。
|
||||
|
||||
邮件未送达时才进入兜底:管理员通过现有 `reset-password` 接口生成一次性密码,受控现场只显示一次并当面交付;接口强制首次改密、清除锁定、撤销旧会话、使旧凭据失效并写审计。兜底写入需在实际发生前再次获得批准。
|
||||
|
||||
## 5. Failure Matrix
|
||||
|
||||
- 邮件验证码未收到:先核对投递状态和企业邮箱可用性;不反复请求导致限流。确认邮件路径不可用后才考虑管理员一次性密码。
|
||||
- 企微内无法同时看登录页和邮箱:改用电脑浏览器完成忘记密码,手机只收验证码;不要为看邮件关闭正在执行 binding 的企微 WebView。
|
||||
- 验证码过期/错误:由员工重新请求;不得让管理员代收或索取验证码。
|
||||
- 页面显示已发送但未看到新邮件:先只读确认是否真正签发新 code、有效期、失败次数和邮件发送失败计数。新签发会使旧 code 失效;不得混用旧邮件,也不得在请求上限内连续点击重发。
|
||||
- 旧验证码被拒绝:归类为“新签发使旧码失效”,证明失败发生在 `/reset-password` 成功之前;不得据此推断前端缓存或首次改密标记未清除。
|
||||
- 正确验证码重设成功后仍跳到改密页:才归类为前端状态/缓存问题,并记录跳转 URL、页面和报错;不要与旧码失效混为一类。
|
||||
- 点击登录页“忘记密码”只进入找回页面,不会签发验证码。只有填写企业邮箱并点击“发送验证码/重新发送验证码”才会请求签发;页面导航次数不能作为验证码请求次数。
|
||||
- 账号或邮箱锁定:只读确认锁定类别和窗口;使用现有解锁接口前另行批准。
|
||||
- 企微仍显示 unbound:确认是否在企微内打开、OAuth 回调是否取得内部 userid、现有 binding 是否指向另一账户;不得按姓名猜测绑定。
|
||||
- binding conflict:失败关闭,保留原映射和审计,单独核实身份后再提出精确修正。
|
||||
- 邮箱验证码登录成功但必须改密页面要求未知旧密码:说明员工走了 `/email-login` 路径;它不同于会清除首次改密标记的 `/reset-password`,返回忘记密码流程,不绕过旧密码校验。
|
||||
|
||||
## 6. Rollback
|
||||
|
||||
- 员工自助重设密码后旧密码和旧会话按设计失效,无需回滚到旧密码。
|
||||
- 新 binding 若经本人验证仍错误,只能在精确确认原/目标映射后使用现有解绑/绑定路径恢复;不得直接删除数据库记录。
|
||||
- 任一员工阻断不影响其他员工逐人完成,但全员验收保持未完成并记录缺口计数。
|
||||
|
||||
## 7. Prevention
|
||||
|
||||
新账号从创建/导入进入 active 后,不得只以“有密码哈希/有 binding 行”宣告可用。启用完成必须包含安全凭据交付、首次改密、密码登录和企微成员本人工作台免登四项;缺一项则保持 onboarding 未完成并由管理员跟踪。
|
||||
|
||||
## 8. Compatibility And Audit
|
||||
|
||||
- 已有 binding 的 3 个账户保持原路径,不额外请求通讯录详情。
|
||||
- 自动补绑定使用现有 `wecom_identity_bindings` 唯一约束,并在同一事务内二次核对 userid 与员工两侧冲突。
|
||||
- 新审计动作只记录匹配方式和布尔字段,不记录完整 userid、邮箱或企微原始 payload。
|
||||
- 未绑定页面不再指示员工输入密码完成正常绑定;错误只显示稳定类别,完整身份资料不得进入 URL、日志或前端状态。
|
||||
- 生产回填先 dry-run 汇总唯一匹配、无匹配和冲突计数,另行批准后才 apply。
|
||||
@@ -0,0 +1 @@
|
||||
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
|
||||
@@ -0,0 +1,51 @@
|
||||
# 全员登录可用性实施计划
|
||||
|
||||
## 1. Read-only Baseline
|
||||
|
||||
- [x] 核对公网 `/ready` 和认证 capabilities;企业邮箱验证码登录、忘记密码均启用。
|
||||
- [x] 聚合核对 4 个 active 账户的邮箱/手机号、密码状态、锁定、企微 binding 和成功登录类型,不输出个人标识或认证数据。
|
||||
- [x] 确认唯一管理员缺企微 binding,其余 3 个 active 账户已有 binding;当前企微成功登录证据为 0。
|
||||
- [x] 确认 3 个账户仍需首次改密且未知随机初始密码无法作为已交付凭据。
|
||||
|
||||
## 2. Administrator Self-Recovery
|
||||
|
||||
- [x] 用户批准进入实施并确认本人可访问企业邮箱。
|
||||
- [x] 首次电脑自助请求已签发新的有效验证码;旧验证码失效后被误用 1 次,账户未锁定,服务端未记录邮件发送失败。
|
||||
- [x] 失败现场已定性并保留:失败发生在 `/reset-password` 成功之前,是第二次签发使旧验证码失效;没有前端缓存跳转证据。
|
||||
- [x] 执行已批准的管理员 break-glass:精确定位唯一 active 管理员,设置受控一次性密码,清除锁定、撤销旧会话、递增 token version、失效旧验证码并写脱敏审计。
|
||||
- [x] 独立只读复核通过:必须首次改密、锁定清零、活动会话与活动验证码均为 0,审计存在;公网 `/ready` 返回 200。
|
||||
- [x] 用户以一次性密码登录并完成强制改密;只读核对首次改密标记已清除、当前仅 1 个活动会话、改密与 break-glass 审计均存在。
|
||||
- [ ] 新版部署后用户从企微工作台重新进入,由 userid/唯一企业邮箱自动补 binding 并直接登录,不输入 OA 密码。
|
||||
- [x] 首次复测仍显示 unbound;只读诊断确认管理员尚无 binding,近 30 分钟没有来自企微 WebView 的成功或失败密码登录,也没有 bind 审计,说明尚未提交页面下方的账号密码表单。
|
||||
- [ ] 用户再次从企微工作台进入,确认直接到 OA 且不再出现 unbound。
|
||||
- [ ] 只读核对管理员新增密码成功、binding 和企微登录审计,输出仅限布尔值。
|
||||
|
||||
## 3. Remaining Active Accounts
|
||||
|
||||
- [ ] 为其余 3 名已有 binding 的 active 员工发送不含账号/密码的操作说明,由本人先直接测试企微工作台免登。
|
||||
- [ ] 每名企微成员从工作台真机进入,验证唯一 binding 与免登;免登通过者不因密码流程而阻塞主入口验收。
|
||||
- [ ] 企微免登验证后,再逐人完成密码回退和首次改密状态收口;管理员不接触其密码或验证码。
|
||||
- [ ] 邮件失败者单独登记稳定失败类别;确需管理员一次性密码时先给出目标数量、审计和交付方案并另行获批。
|
||||
|
||||
## 4. Verification and Prevention
|
||||
|
||||
- [ ] 聚合复核 active 总数、密码登录覆盖、企微 binding 覆盖、企微成功登录覆盖和锁定数;不得输出人员明细。
|
||||
- [ ] 验证无 binding conflict、无角色变化、无企微通讯录写入、无异常会话残留。
|
||||
- [x] 更新现有管理员手册/试运行 checklist:active 账号启用必须包含安全凭据交付、首次改密、密码登录和企微本人免登。
|
||||
- [x] 运行认证聚焦测试、前端 typecheck/build、Trellis task/governance、敏感扫描、文档审计和 `git diff --check`;生产部署后的公网 smoke test 仍待独立批准后执行。
|
||||
|
||||
## 4A. Passwordless First WeCom Login
|
||||
|
||||
- [x] 抽取只读企微成员资料获取能力:输入 OAuth 已确认的内部 userid,只返回规范化企业邮箱和必要状态,不记录原始 payload。
|
||||
- [x] 普通企微登录在 binding 缺失时,按 userid/唯一企业邮箱匹配既有 active OA 账户;事务内二次检查双向冲突、建立 binding、写脱敏审计并创建会话。
|
||||
- [x] 对无匹配、inactive、userid 已绑定他人、账户已绑定其他 userid 和企微 API 失败逐类失败关闭;不按姓名绑定、不创建账户、不改角色。企微邮箱缺失时仅允许 userid 精确匹配。
|
||||
- [x] 保持已有 binding 快路径不变,避免每次登录都调用通讯录详情接口;密码登录不再消费 pending bind 建立企微映射。
|
||||
- [x] 修正 unbound 页面文案:不再把账号密码作为首次企微绑定说明,显示稳定的映射缺失/冲突处理指引。
|
||||
- [x] 增加后端路由与服务测试、前端文案/交互测试;覆盖首次免密自动绑定、已有 binding、负向矩阵和审计脱敏。
|
||||
- [ ] 生成生产存量 dry-run 脱敏汇总;部署和 apply 回填分别取得明确批准后执行。
|
||||
|
||||
## 5. Stop Conditions
|
||||
|
||||
- 企业邮箱不可达、验证码投递异常或账户锁定:停止该员工操作,不连续请求。
|
||||
- 企微回调缺 userid、binding conflict 或映射不唯一:停止写入并只读诊断。
|
||||
- 任何步骤要求在聊天、任务、命令参数或普通日志中暴露密码、验证码、完整账号或 userid:立即停止并改用受控现场交付。
|
||||
@@ -0,0 +1,108 @@
|
||||
# 全员登录可用性
|
||||
|
||||
## Goal
|
||||
|
||||
确保每个 active OA 账户至少有一条实际可验收的登录路径,并为企业微信成员预先建立 userid/企业邮箱到 OA 账户的唯一映射;员工第一次从企微工作台进入即直接登录,不把输入 OA 用户名和密码作为首次绑定步骤。
|
||||
|
||||
## Scope
|
||||
|
||||
- 当前生产 4 个 active 账户的密码回退与企业微信工作台免登基线。
|
||||
- 管理员本人优先通过已批准的受控 break-glass 恢复密码,再绑定企微并真机验收;其余 3 个已有 binding 的账户先验证企微免登,再按需完善密码回退。
|
||||
- 邮件失败时使用现有管理员一次性密码接口作为另行批准的兜底。
|
||||
- 更新现有账号启用/入职操作门禁,防止后续 active 账户再次只有未知随机密码或未经本人验证的 binding。
|
||||
- 改造企微登录缺失 binding 时的唯一企业邮箱自动匹配、事务绑定、审计和失败关闭逻辑;同步修正未绑定页面指引。
|
||||
|
||||
## What I Already Know
|
||||
|
||||
- 管理员本人从企业微信工作台进入时收到“当前企业微信账号尚未绑定”,且没有已知可用密码。
|
||||
- 当前登录页的未绑定流程要求先使用账号密码登录,再到个人设置完成企业微信绑定;这对不知道随机初始密码的导入账户会形成死锁。
|
||||
- 现有管理员手册已经规定:全员应能账号密码登录,已绑定员工还应能从企业微信工作台进入。
|
||||
- OA 已有管理员重置普通账户密码接口:生成一次性密码、强制首次改密、清空锁定、撤销旧会话并写审计。
|
||||
- OA 也支持企业邮箱验证码登录,但真实邮件投递和边缘 challenge 端点是否当前可用必须现场核对,不能假定为可用兜底。
|
||||
- 企业微信普通登录只按回调 userid 查询唯一 binding;找不到 active 绑定就进入 `wecom=unbound`,不会按姓名、邮箱或手机号自动猜测账户。
|
||||
- 生产只读基线为:active 账户 4 个,全部有企业邮箱和密码哈希,3 个有手机号,当前密码/邮箱验证码均无锁定。
|
||||
- 4 个 active 账户中 3 个处于 `must_change_password=true`,但没有实际交付随机初始密码的证据;仅 1 个账户有历史成功密码登录,邮箱验证码成功登录和企微成功登录均为 0。
|
||||
- 3 个 active 账户已有企微 binding;唯一 active 管理员正是缺 binding 的账户,且该管理员已有历史密码成功登录、当前无需强制改密但本人已不知道密码。
|
||||
- 企业微信通讯录本身拥有管理员 userid,但生产只读核对显示 OA 侧没有该管理员的目录同步 userid、成功 HR 主数据映射或 identity binding;当前 OAuth 回调只能拿到 userid,无法仅凭它反推出唯一 OA 账户。
|
||||
- 现有企微 HR 主数据同步已经能够按 binding、userid/员工编号、手机号或企业邮箱解析员工并在 apply 时写入 binding,但当前普通 `mode=login` 回调只查 binding;binding 缺失时不会利用企微通讯录企业邮箱匹配既有 OA 账户。
|
||||
- 公网认证能力当前报告企业邮箱验证码登录和忘记密码均已启用。
|
||||
- `/forgot-password -> /reset-password` 是完整的验证码重设密码路径;正确验证码会设置新密码并清除 `must_change_password`,不是死路。
|
||||
- 本次实际失败发生在 `/reset-password` 成功之前:第二次签发使第一次验证码失效,用户收到并使用旧验证码后被后端拒绝;未发现“重置成功后被前端缓存再次送回改密页”的现场证据。
|
||||
- `/email-login/code -> /email-login/verify` 是另一条邮箱验证码登录路径;现有前端在验证成功后仍遵守 `must_change_password` 跳转,而修改密码页强制要求旧密码,因此它对未知随机初始密码账户可能形成死路,但与本次失败无关。
|
||||
|
||||
## Requirements (Evolving)
|
||||
|
||||
- 登录基线固定为双路径:每个 active 账户都必须具备可实际交付并完成首次改密的账号密码回退;属于当前企业微信通讯录的 active 员工还必须具备唯一 binding 和工作台免登。
|
||||
- 先以只读方式核对所有 active 账户的状态、可登录标识、密码是否为强制首次改密状态、登录锁定、企业微信 binding、最近成功登录类型和企业邮箱验证码能力;只输出计数和脱敏状态。
|
||||
- 把“数据库里存在密码哈希”与“员工实际掌握可用凭据”区分开,后者必须由一次真实登录或受控的一次性密码交付证明。
|
||||
- 管理员本人先通过已批准的受控 break-glass 恢复一条安全登录路径,再完成企业微信绑定和工作台真机登录,不通过聊天、命令参数、环境变量、Trellis、日志或仓库传递密码。
|
||||
- 其余 3 个已有 binding 的 active 账户先直接验证企业微信工作台免登;只有免登不通或需要独立回退时,才进入密码找回或管理员一次性密码流程。
|
||||
- 企微成员首次工作台登录不得依赖 OA 密码:已有 userid binding 直接登录;binding 缺失时,仅允许使用企微接口返回的企业邮箱精确匹配唯一 active OA 账户,事务内创建 binding、写审计并继续登录。
|
||||
- 企业邮箱缺失、无 active OA 匹配、目标账户已绑定另一 userid、userid 已绑定另一账户或企微资料接口失败时必须失败关闭;姓名只用于人工核对,不得作为自动绑定键。
|
||||
- 自动匹配只允许绑定既有 active OA 账户,不得在普通登录中自动创建员工、修改角色、更新通讯录或覆盖冲突 binding。
|
||||
- 通讯录/HR 主数据同步应在账号启用前建立 binding;登录时自动补绑定仅作为确定性兜底,不替代入职门禁。
|
||||
- 对每个 active 账户建立可验收的账号密码回退;属于当前企业微信通讯录的 active 员工还应具备唯一 binding 和工作台免登。
|
||||
- 当前 4 个 active 账户逐人验收后,把“密码回退已完成 + 企微成员免登已完成”写入新账号启用/入职门禁,避免后续导入账户再次只有未知随机密码。
|
||||
- 密码重置必须使用现有审计接口或经单独批准的受控 break-glass 路径;生成的一次性密码仅在受控现场显示一次,首次登录立即改密。
|
||||
- 任何 binding 修正必须先证明目标 OA 员工和企微 userid 唯一对应;不得按姓名模糊匹配,不得修改企业微信通讯录成员。
|
||||
- 生产 binding、密码、账户状态或认证配置写入前另行给出精确变更清单并取得批准。
|
||||
|
||||
## Decision: Credential Recovery
|
||||
|
||||
**Context**:管理员缺企微 binding 且未知密码,忘记密码现场又出现第二封验证码未送达;其余 3 个 active 账户已有企微 binding,但尚无真实企微登录记录。让管理员接触所有员工密码会扩大敏感信息处理面。
|
||||
|
||||
**Decision**:管理员先执行已批准的受控 break-glass,随后绑定企微并验证免登;其余 3 人先直接验证企微免登。只有免登不通或需要建立独立密码回退时,员工才使用忘记密码;邮件确实无法送达时,再由管理员使用审计能力生成一次性密码并线下当面交付。
|
||||
|
||||
**Consequences**:先验证系统预期主入口,避免让 3 名员工重复经历尚未稳定的邮件找回流程;最终双路径基线不变。任何一次性密码都不得经聊天、任务、邮件正文、命令参数、环境变量或普通日志转发。
|
||||
|
||||
## Decision (ADR-lite)
|
||||
|
||||
**Context**:仅保证一种登录方式会在企微 binding 漂移或未知随机初始密码时再次形成登录死锁。
|
||||
|
||||
**Decision**:采用“双路径基线”。账号密码是每个 active 账户的独立回退;企业微信成员在此基础上必须完成工作台免登。
|
||||
|
||||
**Consequences**:需要逐人安全交付一次性密码并完成首次改密,也需要逐人企微真机验收;实施工作略多,但能避免任何单一入口故障把员工锁在 OA 外。
|
||||
|
||||
## Acceptance Criteria (Evolving)
|
||||
|
||||
- [x] 管理员本人能使用一次性凭据完成首次改密,且凭据未进入聊天、任务材料、日志或仓库。
|
||||
- [ ] 管理员本人能从企业微信工作台直接进入 OA,不再出现未绑定提示。
|
||||
- [x] 一个“无 binding、企微企业邮箱唯一匹配 active OA 账户”的合成场景能在不输入 OA 密码的情况下自动建立 binding、写审计并直接登录。
|
||||
- [x] 企业邮箱缺失、无匹配、inactive、userid/binding 冲突等负向场景全部失败关闭,且不创建账户、不改角色、不按姓名绑定。
|
||||
- [ ] 所有 active 账户均有一次实际账号密码登录验收或等价的安全凭据交付与首次改密证据。
|
||||
- [ ] 所有属于当前企业微信通讯录的 active 账户均有唯一 binding,并完成本人企微真机登录验收。
|
||||
- [ ] 不存在 active 账户同时缺少可交付密码回退和可用企微 binding。
|
||||
- [ ] 所有重置、绑定、解锁、会话撤销均有 OA 审计且未扩角色、未修改企业微信通讯录。
|
||||
- [ ] 自动化、Trellis、敏感扫描、生产 `/ready` 和关联认证 smoke 通过后才通知员工测试。
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- 只读基线、写入前后差异、逐人登录验收和剩余阻断均以脱敏计数记录。
|
||||
- 相关后端测试、前端 typecheck/build、认证 smoke、Trellis 治理和敏感扫描通过。
|
||||
- 生产变更具备最小回滚:binding 修正可恢复原映射,密码重置通过 token version 和会话撤销确保旧凭据失效。
|
||||
- 现有管理员手册和试运行事实源与最终登录基线一致。
|
||||
|
||||
## Test Plan
|
||||
|
||||
- 生产前后只读聚合:active 总数、必须改密数、锁定数、binding 数和各成功登录类型覆盖数。
|
||||
- 管理员本人及其余 active 员工分别完成真实密码登录与企业微信工作台免登;人工证据只记录 PASS/BLOCKED 和稳定原因。
|
||||
- 若发生管理员一次性密码兜底,核对强制改密、旧会话撤销、token version 和审计增量,不记录密码正文。
|
||||
- 运行认证聚焦后端测试、前端 typecheck/build、生产 `/ready` 与认证 capabilities、公网只读 smoke。
|
||||
- 运行 Trellis task/governance、敏感扫描、文档审计、流程合同和 `git diff --check`。
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- 不在 OA 中创建或修改企业微信成员。
|
||||
- 不把真实密码、完整账号、手机号、邮箱、企微 userid 或认证原始 payload 写入任务证据。
|
||||
- 不启用自助 OAuth onboarding,不重构认证架构,不放宽登录、绑定或账户管理权限。
|
||||
- 本轮不修改“邮箱验证码登录后首次改密仍要求旧密码”的认证流程。
|
||||
- 不启用会自动创建 OA 员工的通用企微 OAuth self-provisioning;本轮普通登录只允许匹配并绑定既有 active OA 账户。
|
||||
- 不顺带导入剩余通讯录成员、修改角色、主管或业务审批规则。
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- 企微登录入口与 binding:`server/src/routes/wecomAuth.js`。
|
||||
- 账号密码和邮箱验证码:`server/src/routes/auth.js`;普通密码登录不再消费 pending bind 建立企微映射。
|
||||
- 管理员一次性密码重置:`server/src/routes/users.js`。
|
||||
- 当前产品事实源:`docs/管理员手册.md` 第 10、12 节。
|
||||
- 安全边界:`.trellis/spec/hyb-oa/security-privacy.md`、`.trellis/spec/hyb-oa/wecom-boundary.md`。
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "all-account-login-readiness",
|
||||
"name": "all-account-login-readiness",
|
||||
"title": "全员登录可用性",
|
||||
"description": "核对并修复 active OA 账户登录路径,实现 userid/唯一企业邮箱预绑定或自动补绑定,保证员工首次从企微工作台进入无需 OA 密码即可直接登录。",
|
||||
"status": "in_progress",
|
||||
"dev_type": "operations",
|
||||
"scope": "authentication",
|
||||
"package": "hyb-oa",
|
||||
"priority": "P2",
|
||||
"creator": "shunli",
|
||||
"assignee": "shunli",
|
||||
"createdAt": "2026-07-28",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "用户将首次企微免密登录设为硬性要求:OA 账户须通过 userid 或唯一企业邮箱预绑定/自动补绑定,不允许首次输入 OA 密码。已有 binding 快路径及 userid/企微企业邮箱精确匹配既有 active OA 账户的事务补绑定已实现,本地后端全量测试、前端构建、E2E 与治理验证通过;姓名不作为键,缺失/无匹配/inactive/冲突/API 失败全部关闭,不自动创建账户或改角色。等待生产部署和存量映射 dry-run/apply 的独立批准。",
|
||||
"meta": {}
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户