增加 PC 企业微信扫码登录(07-30-pc-wecom-qr-login)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-07-30 17:16:22 +08:00
父节点 45dc206a4c
当前提交 fb164eb690
共修改 21 个文件,包含 959 行新增和 22 行删除
+101 -1
查看文件
@@ -340,7 +340,8 @@ business role assignment -> separate administrator-audited operation
- 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`.
`AUTH_WECOM_LOGIN_SUCCESS`; the latter records only `bindingSource` and the
fixed allowlisted `entrySource`.
### 3. Contracts
@@ -411,6 +412,105 @@ OAuth userid -> existing binding OR exact userid/unique enterprise email match
-> transactional binding -> redacted audit -> direct WeCom session
```
## Scenario: Hosted PC WeCom Web Login
### 1. Scope / Trigger
- Trigger: adding or changing ordinary-browser WeCom login, the public WeCom
config response, OAuth state/callback handling or login-entry audit.
- Purpose: let an existing OA employee use the WeCom-hosted Web login page from
a normal PC browser without loading third-party scripts beside OA password or
email-code inputs.
### 2. Signatures
- Public config: `GET /api/auth/wecom/config` returns
`pcWebLoginEnabled: boolean` and `pcLoginUrl: string | null` separately from
the workbench `loginUrl`.
- OA entry: `GET /api/auth/wecom/login?returnTo=<internal-path>&entrySource=pc_qr`.
- Provider entry: `https://login.work.weixin.qq.com/wwlogin/sso/login` with
`login_type=CorpApp`, CorpID, AgentID, callback and signed state.
- Shared callback: `GET /api/auth/wecom/callback?code=<one-time-code>&state=<signed-state>`.
- Env gate: `WECOM_PC_WEB_LOGIN_ENABLED=false` by default.
- Audit source: `entrySource=pc_qr | wecom_workbench`; `mode` remains
`login | bind | onboard`.
### 3. Contracts
- The OA login page uses a normal redirect to the WeCom-hosted page. Do not add
`@wecom/jssdk`, a WeCom CDN script or a QR iframe to the credential page.
- `entrySource` is independent of `mode`, is validated against a fixed server
allowlist and never accepts `pc_qr` for bind/onboard. Missing source in an
in-flight legacy state means `wecom_workbench`; an unknown signed value fails.
- PC state remains a short-lived HMAC-signed payload and is correlated to the
initiating browser with a short-lived HttpOnly, Secure-in-production,
SameSite=Lax cookie. Validate and clear it before code exchange.
- The cookie is a CSRF/browser-correlation control, not server-side nonce
storage. Normal-browser replay fails after cookie clearing; deliberate replay
of both URL and old cookie is finally stopped by WeCom's one-time, five-minute
code contract and must not create a second OA session.
- Reuse existing userid mapping, fail-closed auto-binding, session limit,
device fingerprint, returnTo allowlist and structured mapping notices.
- Successful login audit contains only `bindingSource` and `entrySource`.
Code, state, nonce, cookies, full userid, Secret and raw provider payload stay
out of logs, audit and browser error details.
### 4. Validation & Error Matrix
- PC env gate off or base entry config incomplete -> `pcLoginUrl=null`; direct
PC entry is unavailable while workbench/password/email-code behavior remains.
- `pc_qr + bind/onboard` or unknown query source -> validation failure before
redirecting to WeCom.
- Tampered/expired state, unknown callback source, missing/mismatched/expired
correlation cookie or missing code -> fail before provider code exchange.
- Signed PC state after the PC gate is turned off -> clear the cookie and fail.
- Existing binding/unique auto-match -> shared session flow; mapping required,
conflict or inactive -> existing stable notice and no session.
- Replayed URL plus old cookie -> provider rejects the already-consumed code;
OA session and success audit counts remain unchanged.
- External/absolute/protocol-relative returnTo -> safe internal default.
### 5. Good/Base/Bad Cases
- Good: normal PC browser redirects to the hosted page, scans, returns with a
correlated callback and reaches the requested internal OA page.
- Base: WeCom workbench continues through `snsapi_base` with
`entrySource=wecom_workbench`; missing source remains compatible during a
rolling deployment.
- Bad: add `mode=qr`, load an SDK next to password fields, expose Secret in the
frontend, accept callback without browser correlation, or claim a signed
cookie is a server-consumed nonce.
### 6. Tests Required
- Route tests assert config gate, exact hosted host/path and parameters, signed
source/state, secure cookie attributes and PC bind/onboard rejection.
- Callback tests assert missing/mismatched/expired cookie, expired/tampered
state, missing code, unknown source and gate-off failures occur before fetch.
- Success/replay tests assert shared mapping and session behavior, sanitized
`entrySource`, cookie clearing and exactly one created session.
- Frontend E2E asserts PC-only button, safe returnTo/device fingerprint and no
duplicate button in a WeCom user agent; typecheck and production build pass.
- Run full backend tests, Prisma validation, external/readiness contracts,
sensitive-content scan, process/Trellis gates and `git diff --check`.
### 7. Wrong vs Correct
#### Wrong
```text
normal browser -> mode=qr -> third-party SDK on OA credential page
-> callback trusts signed state alone -> audit raw userid
```
#### Correct
```text
normal browser -> entrySource=pc_qr -> OA sets correlation cookie
-> WeCom-hosted login -> shared callback validates state + cookie
-> one-time code -> existing userid mapping -> sanitized session audit
```
## Scenario: WeCom Directory Export With Optional Phone
### 1. Scope / Trigger