实现企微首次免密自动绑定(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
|
||||
|
||||
在新工单中引用
屏蔽一个用户