实现企微首次免密自动绑定(07-28-all-account-login-readiness)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-07-28 11:18:50 +08:00
父节点 0551212b78
当前提交 9b12451b85
共修改 22 个文件,包含 1764 行新增和 132 行删除
+91
查看文件
@@ -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