Skip to content

feat: OAuth2 registration with email completion flow - #10202

Open
JohnNiang wants to merge 22 commits into
halo-dev:mainfrom
JohnNiang:feat/oauth2-registration-email-completion
Open

feat: OAuth2 registration with email completion flow#10202
JohnNiang wants to merge 22 commits into
halo-dev:mainfrom
JohnNiang:feat/oauth2-registration-email-completion

Conversation

@JohnNiang

@JohnNiang JohnNiang commented Aug 4, 2026

Copy link
Copy Markdown
Member

What type of PR is this?

  • Feature

What this PR does / why we need it:

OAuth2 未绑定用户可选择绑定已有账号或一键注册;注册无可用邮箱/未验证邮箱的用户被门禁引导到 /complete-profile 页面补填邮箱(按"注册需验证邮箱"设置决定是否必须验证码),超级管理员豁免。

Summary of changes:

  • User.email schema — 移除 @Schema(requiredMode = REQUIRED),变为可选
  • OAuth2 registration service — 用户名/显示名/邮箱推导、随机兜底、幂等、创建用户与连接及失败回滚(8 tests)
  • OAuth2 selection page (/login?oauth2_select) — 未绑定用户选择"一键注册"或"绑定已有账号";POST /login/oauth2/register 注册并登录(4 tests)
  • MapOAuth2AuthenticationFilter — 未绑定重定向从 /login?oauth2_bind 改为 /login?oauth2_select(1 test)
  • UserService.checkEmailInUse — 检查邮箱是否已被其他用户验证使用,排除自身(2 tests)
  • Complete profile page (/complete-profile) — 补填邮箱 + 验证码提交,含登出按钮和发送验证码错误处理修复(5 tests)
  • EmailCompletionFilter — 门禁 WebFilter:未验证邮箱用户 GET HTML 请求重定向到 /complete-profile,API 请求返回 403 (email-not-set)(8 tests)
  • Authorization/complete-profile/** 要求已认证
  • CSRF hidden inputs — 两个新表单添加 CSRF 令牌
  • OpenAPI + api-client 重新生成 — email 属性改为可选

Testing:

  • ./gradlew build — BUILD SUCCESSFUL(263 api + 1612 application tests)
  • pnpm -C ui typecheck && pnpm -C ui lint — PASS
  • 14 commits, 每个任务经过独立 spec + quality 审阅 + 最终全分支审阅 + 修复波次重新审阅

Special notes:

  • DefaultOAuth2RegistrationService 构造注入 Clock,在 WebServerSecurityConfig 中添加了 @Bean Clock clock()
  • 门禁过滤器的 switchIfEmptydefaultIfEmpty(false) 修复了原设计中的链式双调度 bug
  • 补邮箱无验证码保存路径会重置 emailVerified=false(防止已验证邮箱替换导致密码重置账户接管)

Fixes #8120

@pkg-pr-new

pkg-pr-new Bot commented Aug 4, 2026

Copy link
Copy Markdown

Open in StackBlitz

@halo-dev/api-client

npm i https://pkg.pr.new/@halo-dev/api-client@10202

@halo-dev/components

npm i https://pkg.pr.new/@halo-dev/components@10202

@halo-dev/richtext-editor

npm i https://pkg.pr.new/@halo-dev/richtext-editor@10202

@halo-dev/ui-shared

npm i https://pkg.pr.new/@halo-dev/ui-shared@10202

@halo-dev/ui-plugin-bundler-kit

npm i https://pkg.pr.new/@halo-dev/ui-plugin-bundler-kit@10202

commit: 87c5382

The post-processing of MapOAuth2AuthenticationFilter read the OAuth2
authentication from the reactive context, which is scoped to the OAuth2
login filter's own downstream and never reaches the filter's
post-processing scope. As a result, binding never happened and the
session was left with the raw OAuth2AuthenticationToken, so the user
was redirected to /uc and then bounced to /login.

Load the authentication from the session security context repository
instead (falling back to the reactive context), and skip
OAuth2AuthenticationToken when resolving the pre-authentication to bind
with to avoid binding under the OAuth2 subject name.
- Show a provider identity card (logo, name) on the selection page
- Promote bind to a full-width secondary button and explain when
  registration is closed
- Add contextual hints, autocomplete/one-time-code attributes and an
  optional badge for the verification code on the complete profile page
- Localize complete profile field errors via the central i18n bundle
- Keep the implementation plan templates in sync
Resolve the username candidate from common user-info attribute keys
(login, username, user_name, preferred_username, nickname) before
falling back to OAuth2User.getName(), which is the opaque sub claim for
OIDC providers and usually produces an unusable username.
@JohnNiang JohnNiang added the kind/feature Categorizes issue or PR as related to a new feature. label Aug 5, 2026
@JohnNiang JohnNiang added this to the 2.26.0 milestone Aug 5, 2026
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.53608% with 60 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.83%. Comparing base (42c374d) to head (87c5382).
⚠️ Report is 709 commits behind head on main.

Files with missing lines Patch % Lines
...ation/oauth2/DefaultOAuth2RegistrationService.java 73.27% 13 Missing and 18 partials ⚠️
...alo/app/security/preauth/AgreementPageFetcher.java 38.88% 10 Missing and 1 partial ⚠️
...p/security/completion/EmailCompletionEndpoint.java 90.90% 5 Missing and 3 partials ⚠️
...ity/preauth/PreAuthOAuth2RegistrationEndpoint.java 92.42% 5 Missing ⚠️
...app/security/completion/EmailCompletionFilter.java 94.33% 1 Missing and 2 partials ⚠️
...lo/app/security/preauth/PreAuthSignUpEndpoint.java 50.00% 2 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main   #10202      +/-   ##
============================================
+ Coverage     59.55%   64.83%   +5.27%     
- Complexity     3812     4877    +1065     
============================================
  Files           677      750      +73     
  Lines         23248    26154    +2906     
  Branches       1500     1822     +322     
============================================
+ Hits          13846    16956    +3110     
+ Misses         8764     8314     -450     
- Partials        638      884     +246     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

- fix: reject email completion when edited email differs from the one the
  code was sent to (complete-profile.error.email-mismatch)
- fix: intercept non-GET HTML requests in EmailCompletionFilter and treat
  user lookup failures as intercepting (fail-closed)
- fix: reuse the fetched system setting in DefaultOAuth2RegistrationService
  to avoid racing the email completion gate
- rename: checkEmailInUse -> checkVerifiedEmailInUse in UserService
- extract: AgreementPageFetcher, RedirectUtils, OAuth2AuthenticationSession
  to remove duplicated preauth/redirect/session-establish logic
- test: cover email-in-use, email mismatch, send-code rate limiting,
  non-GET redirect, fail-closed interception and OAuth2 token cleanup
  after registration
@sonarqubecloud

sonarqubecloud Bot commented Aug 5, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/feature Categorizes issue or PR as related to a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Request support for auto-registering after logging in with OAuth2

1 participant