feat(auth): provider 抽象消灭 switch + M2b 绑定流程 + 模块 README - #57
Open
longsizhuo wants to merge 2 commits into
Open
Conversation
设置页的"已绑定登录方式"对多数人只显示光秃秃的 "GitHub",而 Discord 那行能 显示 "Discord · 某某"。原因不在前端:ensureIdentity 在身份行已存在时只调 touchLastLogin 刷新时间戳,把手里本次登录拿到的 email / displayName 丢掉了。 M0 回填建出来的行只有 (user_id, provider, provider_user_id),email_at_link 和 display_name_at_link 一直是空的,而只更新时间戳意味着**再登录多少次也补不上**。 实测 github 55 行里 51 行缺展示名、55 行全部缺邮箱。 touchLastLogin → recordLogin(id, email, displayName):一条 UPDATE 同时刷新 last_login_at 并用 COALESCE 补齐空列。只填空值,不覆盖已有值——列名的 at_link 语义是"绑定当时的值",不该被后来的登录改写;本次没拿到邮箱/名字时参数为 null, COALESCE 保持原值,等下次再补。 存量 51 行会在各自下次登录时自愈,不需要数据迁移。 UserIdentityRepositoryTests 补一条:空行被补齐 / 已有值不被覆盖 / 传 null 不抹空。 全量 301 测试通过。
## 为什么
接一个新登录 provider 原本要改 6 处,其中 3 处是散落的 switch(provider):
authRequestFor / isProviderEmailVerified / redirectUriOf。漏改任一处会得到
"能跳转但邮箱不被信任"这种半死状态——登录看着是成功的,只是悄悄多建了个账号。
接 Google 之前必须先把这个漂移源头堵掉。
## provider 抽象
新增 AuthProvider 接口(key / newRequest / redirectUri / isEmailVerified /
revokeToken)与 AuthProviderRegistry(按 key 收集所有 bean,重复 key 启动即失败,
不留"随机命中一个"的余地)。GithubAuthProvider、DiscordAuthProvider 各自封装
自己的配置与邮箱信任判据。三个 switch 全部删除。
接新 provider = 新增一个类 + 三行配置,不改任何已有文件。
## M2b 绑定流程
/oauth/bind/{provider}(@SaCheckLogin)→ 授权 → 回调挂到当前账号,不建号、
不换会话、不发新 token。与登录共用回调端点,走哪条由服务端意图决定,不由请求参数。
INV-007 红线:绑定目标 userId 绝不进 state。userId 取自发起时已校验的会话,
以随机 state 为 key 存服务端内存(5min TTL、一次性),回调时二次核对当前会话
仍是同一人。state 只是不可猜测的查找键。
冲突给可辨识 code 而非 500:bind_taken(已绑他人)/ bind_duplicate(本账号已绑
同类)/ bind_already_yours / bind_session。绑定 github 补写 github_id,与解绑
时清空对称。
绑定不经过 Discord 灰度闸——闸保护的是"建新号",绑定不建号。
**为什么绑定必须早于 GA**:UNIQUE(provider, provider_user_id) 意味着一个第三方
身份只能绑一个账号。先放开 → 用户被分叉 → 新账号占住该身份 → 本尊补绑撞约束,
从"插一行"变成"跨账号迁移 posts/chat/follows"。顺序错了成本差一个数量级。
## 前端
设置页加"连接"按钮。可绑列表来自新端点 GET /identities/providers(后端已注册的
provider),前端不再维护写死列表——接新 provider 按钮会自动出现。绑定回调结果
(?bind=ok / ?bind_error=)给出可区分文案。用 Suspense 包裹(useSearchParams)。
## 文档
usercenter/README.md:接入新 provider 的完整契约、邮箱信任为何是安全判据而非展示
字段、两条 OAuth 流程、绑定为何不能把 userId 放进 state、账号 vs 身份的关系。
SECURITY.md INV-007 补上绑定流程的实现与两条回归测试。
全量 313 测试通过(301 → +12)。前端 72 通过 / typecheck / lint 0 error /
build 通过,login 仍为 ● SSG(settings 改动前后都是 ƒ,非本次引入)。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
为接入 Google 等新登录方式做的结构准备,外加 M2b 绑定流程。请 review,我不合并。
配套前端 involutionhell#394。
1. 漂移源头:3 个散落的 switch
接一个新 provider 原本要改 6 处,其中 3 处是按 provider 名分发的 switch:
漏改任一处会得到**"能跳转但邮箱不被信任"这种半死状态 —— 登录看着是成功的,只是悄悄多建了一个账号**,极难发现。
新增
AuthProvider接口 +AuthProviderRegistry,三个 switch 全部删除:接 Google = 新增一个类 + 三行配置,不改任何已有文件。 注册表里重复 key 会启动即失败,不留"随机命中一个实现"的余地。
2. M2b 绑定流程
与登录共用回调端点,走哪条由服务端的绑定意图决定,不由任何请求参数决定。
INV-007 红线:userId 绝不进 state
state客户端可伪造。把"绑定到哪个账号"写进去,攻击者就能构造"绑定到我账号"的 state 诱导受害者授权,把受害者的第三方身份绑到自己账号上。实现:userId 取自发起时
@SaCheckLogin校验过的会话 → 以随机 state 为 key 存服务端内存(5min TTL、一次性消费)→ 回调取回并二次核对当前会话仍是同一人(中途登出/换号即拒绝)。state 只是不可猜测的查找键。冲突给可辨识 code 而非 500
bind_takenbind_duplicatebind_already_yoursbind_session绑定 github 补写
github_id,与解绑时清空对称。绑定不经过 Discord 灰度闸——闸保护的是"建新号",绑定不建号。为什么绑定必须早于 GA
UNIQUE (provider, provider_user_id)意味着一个第三方身份只能绑一个账号:顺序错了,成本差一个数量级。
3.
usercenter/README.md接入新 provider 的完整契约、为什么
isEmailVerified是安全判据而非展示字段(返回 true 的门槛是"provider 保证用户控制该邮箱",否则等于把账号接管焊进登录流程)、两条 OAuth 流程、绑定为何不能把 userId 放进 state、账号 vs 身份的关系、剩余 3 处必须手动改的地方及其后果。SECURITY.mdINV-007 补上绑定流程的实现说明与两条回归测试。验证
全量 313 通过(301 → +12):registry 5 条(大小写/未知返回空/重复 key 启动失败/真实 provider 的邮箱信任/半配置拒绝)、绑定 5 条(成功不建号/
bind_taken/bind_duplicate/bind_already_yours/github_id 回填)、绑定鉴权 2 条。未部署,等你 review。