# Mini Program WeChat Auth Design

## Problem

The mini program currently supports phone number plus SMS code login only. The new requirement is to add full WeChat authorization login while keeping the existing phone login available. WeChat login must use the free openid-based login flow as the primary path, and phone number authorization should be integrated when available so the backend can identify and bind existing phone users automatically.

## Goals

- Add WeChat mini program login with `wx.login`/`uni.login` and backend `code2Session` exchange.
- Keep the existing phone plus SMS code login flow unchanged as a supported fallback and alternate login method.
- Use WeChat openid as the free, reliable identity for mini program login.
- If WeChat phone authorization succeeds, use the returned phone number to identify an existing user and automatically bind the WeChat openid to that account.
- Save nickname and avatar into existing `t_user.nickname` and `t_user.avatar` fields when the user provides them.
- Reuse the current JWT response, token storage, session restore, onboarding routing, and profile flow.
- Match the current mini program dark glass/cosmic visual style.

## Non-Goals

- Do not remove, hide permanently, or degrade phone SMS login.
- Do not introduce paid third-party login or SMS providers.
- Do not require phone number authorization before a user can log in with WeChat openid.
- Do not redesign unrelated onboarding, main tabs, or profile features.
- Do not store WeChat `session_key` on the client.

## Current Findings

- `mini-program/src/pages/login/index.vue` contains the existing phone/SMS login UI and routes to main or onboarding based on profile presence.
- `mini-program/src/stores/app.js` already centralizes login, logout, token restore, and profile fetch behavior.
- `mini-program/src/services/auth.js` stores `access_token` and `refresh_token` from `AuthResponse`.
- `server/src/main/java/com/emotion/controller/AuthController.java` exposes `/auth/login`, `/auth/refreshToken`, and token validation endpoints.
- `t_user` already has `phone`, `avatar`, `nickname`, `third_party_id`, and `third_party_type`, which can support WeChat binding without a required schema change.
- The current backend issues JWT plus Redis-backed access/refresh tokens through `AuthServiceImpl`.
- The mini program manifest already has a WeChat appid: `wxaf2eaba72d28f0e4`.
- `application.yml` security ignore URLs must include the new public WeChat login endpoint, while the phone binding endpoint must remain protected by JWT.
- `UserInfoResponse` exposes phone, nickname, and avatar, but it does not expose whether the current account has a WeChat binding. The frontend needs a non-sensitive `wechatBound` boolean instead of receiving openid.
- Binding an openid from a temporary WeChat user to an existing phone user must also transfer or preserve data created under the temporary user; otherwise early actions before phone binding can be stranded on the temporary account.

## Recommended Approach

Use a two-track login page:

1. Primary action: WeChat login. The mini program calls `uni.login({ provider: 'weixin' })`, sends the returned code to the backend, and receives the same `AuthResponse` shape used by existing login.
2. Secondary action: phone SMS login. The current phone/code form remains available and continues to call `/auth/login`.

Phone authorization is a post-login enhancement:

1. After WeChat login succeeds, show or surface a "bind phone" action using WeChat `open-type="getPhoneNumber"` when the platform supports it.
2. The mini program sends the phone authorization `code` to the backend.
3. The backend calls WeChat's phone-number API.
4. If the phone already belongs to an existing user, bind the current openid to that existing user and return a fresh `AuthResponse` for that existing user.
5. If the phone does not exist, write it to the current WeChat user and return updated user info.

This keeps WeChat openid login free and resilient while letting phone number authorization unify identities when available.

## Backend Design

### Configuration

Add configuration under `emotion.wechat.mini-program`:

- `app-id`
- `app-secret`
- `base-url`, default `https://api.weixin.qq.com`

Secrets must come from environment/profile configuration in deploy environments. `app-secret` must not be sent to the client or logged.

Add a database migration for WeChat identity integrity:

- Add a unique composite index on `t_user(third_party_type, third_party_id)`.
- MySQL allows multiple `NULL` values in a unique index, so ordinary phone users without WeChat binding are unaffected.
- Before transferring an openid from a temporary WeChat user to an existing phone user, clear the temporary user's third-party fields so the unique index remains valid.

### DTOs

Add request/response DTOs:

- `WechatLoginRequest`
  - `code`: required login code from `uni.login`.
  - `nickname`: optional user-provided nickname.
  - `avatar`: optional user-provided avatar URL.

- `WechatPhoneBindRequest`
  - `code`: required phone authorization code from `getPhoneNumber`.

- The response can reuse `AuthResponse` for login and phone binding so token handling stays consistent.
- Extend `UserInfoResponse` with `wechatBound: boolean`. It must be computed from the server-side third-party fields and must not expose openid or session key.

### Service Boundaries

Add `WechatMiniProgramService` for WeChat API calls:

- `code2Session(code)` returns openid/session metadata.
- `getStableAccessToken()` obtains and caches the mini program interface token in Redis.
- `getPhoneNumber(phoneCode)` returns the verified phone number.
- Translate WeChat API error codes into clear `BusinessException` messages.

Add `UserIdentityMergeService` for account convergence:

- `mergeTemporaryWechatUserIntoPhoneUser(String temporaryWechatUserId, String phoneUserId)` transfers user-owned rows from the temporary WeChat user to the canonical phone user.
- The service updates the core mini-program tables that store `user_id`: `t_user_profile`, `t_life_event`, `t_epic_script`, `t_life_path`, `t_life_path_step`, `t_social_content_item`, `t_social_profile_insight`, `t_user_consent_log`, `t_tts_task`, and `t_analytics_event`.
- It also updates user-facing shared data where appropriate: `t_conversation`, `t_message`, `t_coze_api_call`, `t_emotion_analysis`, `t_emotion_record`, `t_diary_post`, `t_diary_comment`, `t_community_post`, `t_comment`, `t_user_stats`, and `t_ai_call_log`.
- If both users have a singleton row such as `t_user_profile` or `t_user_stats`, keep the existing phone user's row and only transfer rows that do not conflict. This avoids overwriting a mature phone account with sparse temporary data.

Extend `AuthService` with:

- `wechatLogin(WechatLoginRequest request)`
- `bindWechatPhone(String currentUserId, WechatPhoneBindRequest request)`

Extend `UserService` with focused lookup helpers:

- `getByThirdParty(String thirdPartyType, String thirdPartyId)`
- `getByPhone(String phone)` already exists and should be reused for phone identity matching.

The controller stays thin and delegates all identity decisions to the service layer.

### Identity Matching

For WeChat login:

1. Exchange login code for openid.
2. Query user by `third_party_type = "wechat-mp"` and `third_party_id = openid`.
3. If found and enabled, issue tokens for that user.
4. If not found, create a user:
   - `account = "wx_" + openid`
   - `username =` generated username
   - `nickname =` request nickname or generated username
   - `avatar =` request avatar if present
   - `third_party_type = "wechat-mp"`
   - `third_party_id = openid`
   - `member_level = "free"`
   - `status = 1`

For phone binding:

1. Require a valid current JWT.
2. Resolve the current WeChat user's openid from `third_party_id`.
3. Exchange the phone authorization code for phone number.
4. Query existing user by phone.
5. If no phone user exists, set current user's `phone` and mark verified.
6. If the phone user is the current user, ensure openid and phone are both present.
7. If another enabled user owns the phone, treat that phone user as canonical.
8. If the phone user already has a different WeChat openid, reject with a clear "phone already bound to another WeChat account" message.
9. If the phone user can accept the binding, transfer user-owned rows from the temporary WeChat user to the phone user, clear the temporary user's third-party fields, bind the openid to the phone user, and issue new tokens for the phone user.

When the phone belongs to an existing account, the existing account is the canonical identity. The response must return tokens for that account so future openid login lands on the same user as phone SMS login.

## Frontend Design

### Login Page

Update `mini-program/src/pages/login/index.vue` while preserving the current visual language:

- Keep the dark background, glass card, purple accents, and safe-area handling.
- Add a primary WeChat login button at the top of the card.
- Keep the existing phone number and SMS code form as an alternate method below a subtle divider.
- Use one `loading` state for the active login method and prevent duplicate taps.
- On successful login, reuse current routing:
  - has profile -> `/pages/main/index?tab=script`
  - no profile -> `/pages/onboarding/index`

### Frontend Services And Store

Add to `mini-program/src/services/auth.js`:

- `wechatLogin({ code, nickname, avatar })`
- `bindWechatPhone({ code })`

Both methods store returned tokens exactly like existing `login()` and `refreshToken()`.

Add to `mini-program/src/stores/app.js`:

- `loginWithWechat(profilePayload)`
- `bindWechatPhone(phoneCode)`
- `fetchCurrentUserInfo()`, called after login, token restore, and phone binding.
- Keep `login(phone, smsCode)` unchanged.
- After phone binding returns new tokens, refresh current profile/user state.

### Phone Authorization UI

Add a small phone-bind entry in the logged-in experience, preferably in `MineView.vue` or onboarding completion area:

- Show only when logged in, `userInfo.phone` is empty, and `userInfo.wechatBound` is true.
- Use WeChat `button open-type="getPhoneNumber"` on mp-weixin.
- On non-WeChat/H5 builds, show the existing phone SMS login/bind fallback.
- If authorization succeeds and backend returns tokens for an existing phone user, overwrite local tokens and refresh profile.
- If the user denies authorization, keep them logged in and show a non-blocking message.

### Nickname And Avatar

Use WeChat's current nickname/avatar collection pattern in profile or onboarding:

- Nickname input can populate existing `registrationData.nickname`.
- Avatar selection can populate existing avatar fields.
- Save through existing user/profile update endpoints where possible.
- Do not block WeChat openid login on nickname or avatar.

## Error Handling

- Missing backend WeChat appid/secret: backend returns configuration error; frontend keeps phone login available.
- `uni.login` failure: show a concise "WeChat login failed, please try again later" message and keep phone login visible.
- `code2Session` failure or invalid code: show a concise login failure message.
- Disabled user: reject login consistently with existing auth behavior.
- Phone authorization denied: do not log out; show a non-blocking message that the user can bind a phone later in the profile center.
- Phone API unavailable or permission not enabled: keep openid login active and route user normally.
- Existing phone is bound to another openid: reject binding and ask user to use phone login or contact support.
- Existing phone account has data while temporary WeChat account also has data: transfer non-conflicting temporary rows to the phone account before switching tokens.
- Temporary WeChat account after successful merge: clear its `third_party_type` and `third_party_id` and disable it from future WeChat login matching.
- Network failures: preserve current session restore behavior and avoid clearing tokens unless backend explicitly rejects auth.

## Data Flow

### WeChat Login

1. User taps WeChat login.
2. Mini program calls `uni.login`.
3. Mini program posts `{ code }` to `/auth/wechat/login`.
4. Backend exchanges code for openid through WeChat.
5. Backend finds or creates user by openid.
6. Backend returns `AuthResponse`.
7. Mini program stores tokens, fetches profile, and routes.

### Phone Auto-Binding

1. Logged-in user taps authorize phone.
2. WeChat returns a phone authorization code.
3. Mini program posts `{ code }` to `/auth/wechat/phone`.
4. Backend exchanges code for phone number.
5. Backend finds existing user by phone.
6. Backend binds openid to the phone user or writes phone to current user.
7. If an existing phone user is canonical, backend transfers non-conflicting temporary user data and clears the temporary WeChat identity.
8. Backend returns `AuthResponse`.
9. Mini program replaces tokens and refreshes user/profile state.

## Testing

Backend:

- WeChat login creates a new user for a new openid.
- WeChat login returns the same user for a known openid.
- Phone binding writes phone to current WeChat user when no existing phone user exists.
- Phone binding binds openid to an existing phone user and returns that user's tokens.
- Phone binding transfers temporary WeChat user data to the existing phone user before returning phone-user tokens.
- Temporary WeChat user no longer matches future openid login after a successful transfer.
- Phone binding rejects a phone user already bound to a different openid.
- Existing phone SMS login still works.
- Token validation and refresh still work after WeChat login.
- `/api/auth/wechat/login` is public, and `/api/auth/wechat/phone` rejects unauthenticated requests.
- `UserInfoResponse` returns `wechatBound` without returning openid.

Mini program:

- Build `npm run build:mp-weixin`.
- Login page supports both WeChat and phone SMS login.
- WeChat login success routes through the current profile/onboarding rules.
- Phone login success routes through the current profile/onboarding rules.
- Denying phone authorization does not break logged-in state.
- Successful phone authorization refreshes tokens and profile.
- Personal center reflects nickname/avatar/phone state where available.
- Restored sessions fetch current user info so phone binding visibility is correct after app restart.

## References

- WeChat Mini Program `code2Session`: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-login/code2Session.html
- WeChat Mini Program phone number API: https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html
- WeChat Mini Program avatar/nickname capability: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/userProfile.html
