增加 PC 企业微信扫码登录(07-30-pc-wecom-qr-login)
这个提交包含在:
@@ -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
|
||||
|
||||
在新工单中引用
屏蔽一个用户