# 登录、授权与会话

星线 App 使用星线 Account 完成身份验证，再由聊天服务签发独立的聊天会话。原生客户端通过系统浏览器参与授权，不把 Account 密码交给聊天服务；当前原生协议同时保护 Account 授权码交换和 App 聊天票据交换。

## 地址与身份边界

| 项目 | 地址或规则 |
| --- | --- |
| 聊天 API | `https://chat.thestarline.cn` |
| Account issuer | `https://account.thestarline.cn/oidc` |
| 正式 HTTPS 授权回调 | `https://chat.thestarline.cn/auth/callback` |
| 星线 App 返回入口 | `starlineworkbench://chat-auth` |
| 星企返回入口 | `xingqiworkbench://auth`，仅用于独立星企产品 |
| 授权范围 | `openid email profile phone` |

聊天服务通过 issuer 的 `/.well-known/openid-configuration` 获取授权、令牌和用户信息端点，检查返回 issuer 完全一致，且这三个端点位于配置 issuer 的同源范围。生产启动还要求正式 Account issuer、HTTPS 公网 URL 和服务端机密 OIDC 客户端配置。具体客户端 ID、密钥和注册记录不属于公开示例。

## 原生登录的两层证明

1. App 使用系统安全随机源生成 32 字节随机值，编码为不带填充的 base64url verifier，并计算 `BASE64URL(SHA256(verifier))`。
2. App 调用 `POST /auth/native/start`，发送 `{app:"starline",codeChallenge,screen:"login"}`。注册入口可把 `screen` 设为 `register`。
3. 聊天服务验证挑战是规范的 43 字符 base64url，创建有效期 10 分钟的登录 flow。响应包含 `authorizationURL`、`state`、`expiresAt`。客户端须保存本次 verifier、state 和到期时间，再打开返回 URL；iOS 还保存 API 基地址。
4. 聊天服务另行生成自己的 verifier，与 S256 挑战发送给 Account。原生入口设置 `prompt=login`；注册入口额外设置 `screen_hint=signup`。这层 verifier 留在聊天服务端。
5. Account 将授权码和 state 回调到正式 HTTPS 地址。聊天服务一次性消费 flow，使用 `client_secret_basic`、授权码和服务端 verifier 请求令牌，随后请求用户信息。
6. 聊天服务要求 `sub` 非空，并要求邮箱已验证或手机号已验证。已验证手机号会规范化；显示名按 name、邮箱前缀和默认名称取值，最多保存 80 个 UTF-16 代码单元。
7. 服务端创建有效期 60 秒的一次性 ticket，将 ticket 与原 state 放入 App 返回 URL。App 对返回入口和参数做校验后，调用 `POST /auth/exchange`，提交 `{ticket,state,codeVerifier}`。
8. 服务端核对 state 哈希与 App verifier 的 S256 值，消费 ticket，签发聊天会话；响应为 `{accessToken,expiresAt,user,eventCursor}`。

两层 verifier 是不同随机值：服务端的一层保护 Account 授权码，App 的一层让仅截获自定义 scheme ticket 的程序无法单独换取聊天会话。客户端不能直接拿 Account access token 调用聊天业务接口。

## 到期、消费与失败语义

| 对象 | 默认有效期 | 重放处理 |
| --- | --- | --- |
| 原生登录 flow | 10 分钟 | HTTPS 回调读取时删除；过期或错误回调返回 `login_failed` |
| 兼容 `/auth/start` flow | 40 分钟 | 仅为兼容浏览器入口，不能替代原生证明流程 |
| 聊天 ticket | 60 秒 | 成功换取后删除；错误原生 proof 不签发会话 |
| 聊天 device session | 24 小时 | 每次 Bearer 请求检查哈希与到期时间 |

`expiresAt` 是 Unix 毫秒时间戳。不要把 Account OIDC token 的期限当成聊天会话期限；当前 Account 源码的访问令牌和 ID token TTL 为 900 秒，授权码为 60 秒。它们用于身份协议，不是 App 业务会话的刷新机制。聊天服务当前没有公开 refresh-token 路由。

原生开始接口可能返回 `INVALID_APP`、`INVALID_CHALLENGE`、`INVALID_SCREEN` 或 `LOGIN_UNAVAILABLE`。票据缺失、过期、已消费、state 不匹配或 verifier 不匹配会导致 `INVALID_TICKET`。客户端应重新开始授权；不能删除 proof 参数去尝试兼容 ticket 模式。错误响应形状见[API 参考](https://document.thestarline.cn/app/api-reference.md)。

## 客户端回调和持久化

iOS 只接受预期 scheme 与 host，拒绝用户名、密码、端口、fragment、异常 path、重复 state、重复 ticket/error，以及 ticket 与 error 同时出现。回调 state 必须等于当前未到期 flow，且必须属于同一 API 基地址。iOS 在交换前保存 ticket，支持进程中断后恢复；成功后先持久化 session，再清理 pending 状态，避免清理失败把已成功登录改成失败。

Android 的 pending 仅保存 verifier、state、expiresAt，没有 iOS 的 API 基地址和 ticket 字段；回调通过校验后会先清除 pending，再发起换票。它不能据此获得与 iOS 相同的“换票中进程中断后持久恢复”能力，失败时需重新开始授权。两端的实现与调试方法分别见[iOS](https://document.thestarline.cn/app/ios.md)和[Android](https://document.thestarline.cn/app/android.md)。登录 token 不应写入普通 URL、截图、日志、分析事件或静态网站。iOS 使用 Keychain，Android 使用 Keystore 支持的本地加密存储。

## 使用与退出

除健康检查和授权入口外，聊天 API 使用 `Authorization: Bearer <chat_access_token>`。服务端接受规定格式的 Bearer token，并将其 SHA-256 摘要与未过期 session 记录匹配。`GET /me` 返回当前聊天用户与该用户最新 `eventCursor`，可用于恢复登录后的状态同步。

`POST /auth/logout` 成功时删除当前聊天会话；它不等于 Account 全站退出或撤销其他设备。两端都会清除本机状态，并忽略远端退出请求失败；Android 先清本机再尝试服务端退出，iOS 尝试释放通话、注销推送和服务端退出后清本机。所以“已退出界面”不能证明服务端撤销成功：网络失败时，旧 session 可能持续到 24 小时到期。Account 修改密码或撤销 Account 会话也没有自动联动撤销聊天 session 的实现。

VoIP 设备与具体 session 摘要关联，服务端注销或到期后不会继续被查询为该会话的有效推送目标。本机退出还会停止轮询、释放媒体连接并清理当前账号界面状态；这些本机动作与远端 token 撤销应分别验证。

## 记忆转出许可的独立边界

聊天服务还有 `/memory-export/start` 与 `/memory-export/exchange`，用于当前登录用户的额外转出验证，不能用于普通登录。开始流程有效期 5 分钟；服务端向 Account 的 redeem 接口验证 ticket、state、verifier，并检查返回用户与当前 Account subject 一致。

当前作为权威依据的 Account 主源码未包含对应 redeem 路由；App 工作树随附的历史 Account 副本虽然有实现，也不能替代当前主源码或证明其已部署。因此只能确认聊天侧桥接契约，不能认定该跨服务流程当前可用。客户端本地导出和 Account 验证是否完整闭环，应按照[测试](https://document.thestarline.cn/app/testing.md)取得实际结果后再更新状态。
