Skip to content

feat(usercenter): M2a 身份查看/解绑后端 - #46

Closed
longsizhuo wants to merge 5 commits into
mainfrom
feat/user-identities-m2a-unbind
Closed

feat(usercenter): M2a 身份查看/解绑后端#46
longsizhuo wants to merge 5 commits into
mainfrom
feat/user-identities-m2a-unbind

Conversation

@longsizhuo

Copy link
Copy Markdown
Member

RFC #42 / ADR-001。栈在 M1(#44)上。绑定新 provider(M2b:intent store + callback 分支 + 设置页前端)另开 PR;本 PR 是自包含的查看+解绑,正好闭合 M0 review 的两个已确认发现。

M1 的双写让每次登录都建 identity,所以"列出/解绑已绑定身份"现在就是完整可用的功能。

端点(走 /api/user-center/*,现有 rewrite 覆盖)

  • GET /api/user-center/identities(@SaCheckLogin):列出当前用户已绑定身份,不泄漏 provider_user_id(只回 provider / 绑定时间 / 最近登录 / 绑定时展示名)。
  • DELETE /api/user-center/identities/{provider}(@SaCheckLogin):解绑。

两条安全规则(闭合 review 发现)

  1. 不能解绑最后一种登录方式——防永久锁死。OAuth 用户的随机密码不是可用登录方式、且无法与真实密码可靠区分,故保守地只按"剩余身份数 ≥ 1"判定,不把密码算作兜底(代价:纯密码用户暂不能解绑其唯一绑定,安全方向优先;待密码 sentinel 可靠追踪后细化)。
  2. 解绑 github 同事务清空 github_id——否则 schema.sql 启动回填会按残留列值把身份静默复活(review finding refactor: 重构ai对话方法 #2/feat(user-center): 用户偏好读写 API + preferences JSONB 列 #7)。@Transactional 保证删身份+清列原子。

验证

  • 全量 263 测试通过(+6:列表 / 解绑非最后 / 解绑最后被挡 / 未拥有拒绝 / github 解绑清列 / 匿名 DELETE 401)。
  • 解绑锁死拦截与 github_id 清除都有专门回归测试。

后续

M2b:POST /link/{provider}/start + callback bind 分支(intent store,Caffeine)+ 前端设置页 UI(列表 + 连接/解绑按钮 + 二次授权)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01RtSNVtrEkCsFVHrbt4Zjgi

RFC involutionhell-backend#42 / ADR-001 的第一阶段,纯增量零行为变化:
- user_identities 表:UNIQUE(provider,provider_user_id) + UNIQUE(user_id,provider)
+ provider 小写 CHECK + FK ON DELETE CASCADE
- 启动回填存量 github_id → provider='github' 行,ON CONFLICT DO NOTHING 保幂等
- UserIdentity model + JdbcUserIdentityRepository(沿用 JDBC 惯例)
- 5 个仓库测试覆盖两条 UNIQUE、级联删除、回填幂等
- ADR-001(OKF frontmatter)记录设计决策与 OAuth state 防护协议
生产数据干跑验证:51 个 github_id 账号,0 重复 / 0 非数字 cast,回填无冲突。
全量 251 测试通过。
xhigh review (PR #43) 的已确认问题:
- init.sql 补 user_identities 表:三处 schema 同步惯例(INV-004 教训),
否则 docker-compose 起的库缺表,M1 登录 500
- ADR 的 state 不变量 INV-006 → INV-007:INV-006 已被 PR #41 限流占用
- 回填注释澄清边界:依赖 SPRING_SQL_INIT_MODE=always;只治行缺失不治值变化;
M2 解绑 github 须同时清 github_id 列,否则重启静默复活已撤销绑定
- 仓库层 provider 归一化小写:JustAuth source 名大写,不归一化查询侧静默查空
- insert() javadoc 声明忽略 linkedAt/lastLoginAt(由 DB default 生成)
- 幂等测试改从 classpath 的 schema.sql 机械提取回填语句,不再手抄副本
全量 252 测试通过。
M0 的 user_identities 回填读 user_accounts.github_id,但 docker/init-db/init.sql
建的 user_accounts 只有 7 列、缺 github_id/avatar_url/email/preferences,schema.sql
的 CREATE TABLE IF NOT EXISTS 又补不上已存在表的列——全新 docker 卷首启时回填
直接 column "github_id" does not exist 崩,后端起不来(既有 bug:github 登录 INSERT
本就缺列,M0 把它升级成硬崩)。
- init.sql 的 user_accounts 补齐到与 schema.sql 一致
- .env.example / application.properties 去掉"改成 never"的误导建议:schema.sql
幂等,always 是安全默认;改 never 会让后续新增表在本地缺失(曾踩坑)
- README 加建表两路径说明 + 缺表/缺列排查(down -v 重建卷)
验证:一次性 throwaway 容器跑 init.sql→schema.sql 两遍 exit=0、无缺列崩溃、
user_identities 建成且幂等。全量 252 测试通过。
RFC #42 / ADR-001 第二阶段。
loginByProvider(loginByGithub 委托至此):
- 账号仍按 "{provider}_{id}" 用户名主查(双写期,M3 才翻转到 identity 主查)
- 无论新建/已存在都维护一行 user_identities(identity 双写),缺行惰性补齐,
兜住 M0-M1 窗口回填未覆盖的账号;identity 写失败不阻断登录(同 INV-003 策略)
- github_id 列双写沿用(createUser/updateProfile 已写)
OAuth state/cookie 硬化(INV-007,登录 CSRF):
- render 种 httpOnly + SameSite=Lax 的 ih_oauth_state cookie
- callback 在换 token 前校验 URL state == cookie,缺失/不匹配即拒(error=oauth_state)
- callback 端点保持 github 专用(泛化 AuthRequest 工厂留到 M3 真接第二个 provider)
新增 INV-007 进 SECURITY.md(INV-006 已被限流占用,按流水规则顺延)。
全量 257 测试通过;生产数据核查:51 个 github 账号 username 约定 0 例外。
RFC #42 / ADR-001。绑定新 provider(M2b)走 OAuth 流程另开 PR;本 PR 做查看+解绑,
自包含,闭合 M0 review 的两个发现。
- GET /api/user-center/identities:列出当前用户已绑定身份(不泄漏 provider_user_id)
- DELETE /api/user-center/identities/{provider}:解绑
- 不能解绑最后一种登录方式(防永久锁死;OAuth 随机密码不可靠,保守按剩余身份数判定)
- 解绑 github 同事务清空 github_id 列(否则 schema.sql 启动回填按残留列值静默复活)
- repository 加 deleteByUserIdAndProvider / clearGithubId
全量 263 测试通过(+6:列表/解绑/锁死拦截/未拥有拒绝/清列/匿名 401)。
CopilotAI review requested due to automatic review settings July 19, 2026 11:47

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@longsizhuo
longsizhuo changed the base branch from feat/user-identities-m1 to mainJuly 19, 2026 12:15
@longsizhuo

Copy link
Copy Markdown
MemberAuthor

#47 替代(M2a 分支与 squash 后的 main 冲突,改用从 main cherry-pick 的干净分支)

@longsizhuo
longsizhuo deleted the feat/user-identities-m2a-unbind branch July 19, 2026 12:18
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@longsizhuo