# 认证模块说明（`src/auth`）

## 1. 目标

- 提供系统管理员创建、院内账号密码登录、B 端小程序手机号登录、C 端小程序手机号登录、身份查询。
- 使用 JWT 做认证，院内账号与 C 端小程序账号走两套守卫。

## 2. 核心接口

- `POST /auth/system-admin`：创建系统管理员（需引导密钥）
- `POST /auth/login`：院内账号密码登录，后台与小程序均可复用
- `POST /auth/login/confirm`：院内账号密码多账号确认登录
- `POST /auth/miniapp/b/phone-login`：B 端小程序手机号登录
- `POST /auth/miniapp/b/phone-login/confirm`：B 端同手机号多账号确认登录
- `POST /auth/miniapp/c/phone-login`：C 端小程序手机号登录
- `GET /auth/me`：返回当前院内登录用户上下文
- `GET /c/patients/me`：返回当前 C 端登录账号绑定的患者基础信息

## 3. 院内账号密码登录流程

1. 前端提交 `phone + password`，`role` 与 `hospitalId` 都可以选传。
2. 若仅匹配到 1 个院内账号，后端直接返回 JWT。
3. 若匹配到多个院内账号，后端返回 `loginTicket + accounts` 候选列表。
4. 前端带 `loginTicket + userId` 调用确认接口获取最终 JWT。

## 4. 微信小程序登录流程

### B 端

1. 前端调用 `wx.login` 获取 `loginCode`。
2. 前端调用手机号授权获取 `phoneCode`。
3. 调用 `POST /auth/miniapp/b/phone-login`。
4. 若手机号仅命中 1 个院内账号，后端直接返回 JWT。
5. 若命中多个院内账号，后端返回 `loginTicket + accounts`。
6. 前端带 `loginTicket + userId` 调用确认接口拿最终 JWT。

### C 端

1. 前端同样传 `loginCode + phoneCode`。
2. 后端先校验该手机号是否唯一命中 `Patient.phone`。
3. 校验通过后创建或绑定 `FamilyMiniAppAccount`，并返回 C 端 JWT。

## 5. 鉴权流程

### 院内账号

1. `AccessTokenGuard` 从 `Authorization` 读取 Bearer Token。
2. 校验 JWT 签名、`id`、`iat` 等关键载荷字段。
3. 根据 `id` 回库读取 `User` 当前角色与组织归属。
4. 校验 `iat >= user.tokenValidAfter`，保证旧 token 失效。

### C 端小程序账号

1. `FamilyAccessTokenGuard` 从 `Authorization` 读取 Bearer Token。
2. 校验 C 端 token 的 `id + type=FAMILY_MINIAPP`。
3. 根据 `id` 回库读取 `FamilyMiniAppAccount`。
4. 将 C 端账号上下文注入 `request.familyActor`。

## 6. 环境变量

- `AUTH_TOKEN_SECRET`
- `SYSTEM_ADMIN_BOOTSTRAP_KEY`
- `WECHAT_MINIAPP_APPID`
- `WECHAT_MINIAPP_SECRET`

## 7. 关键规则

- 后台 Web 与 B 端小程序都可以复用 `POST /auth/login` 做账号密码登录。
- 密码登录命中多个院内账号时，不再强制前端手填 `hospitalId`，改为后端返回候选账号供选择。
- B 端小程序登录复用 `User` 表，继续使用 `openId`。
- B 端账号未绑定 `openId` 时，首次小程序登录自动绑定。
- 同一个 `openId` 可绑定多个院内账号，支持同一微信号切换多角色/多院区账号。
- 若目标院内账号已绑定其他微信号，仍需先在用户管理中清空该账号的 `openId` 再重新绑定。
- C 端账号独立存放在 `FamilyMiniAppAccount`。
- C 端手机号必须唯一命中患者档案，否则拒绝登录。
- C 端登录后读取“我的信息”请调用 `GET /c/patients/me`，不要复用 `GET /auth/me`。
- `serviceUid` 仅预留字段，本次不提供绑定接口。
