Skip to content

core: fix oauth callback with mail only#1110

Merged
undefined-moe merged 2 commits intohydro-dev:masterfrom
renbaoshuo:fix/oauth-mail-binding
Dec 26, 2025
Merged

core: fix oauth callback with mail only#1110
undefined-moe merged 2 commits intohydro-dev:masterfrom
renbaoshuo:fix/oauth-mail-binding

Conversation

@renbaoshuo
Copy link
Copy Markdown
Contributor

@renbaoshuo renbaoshuo commented Dec 26, 2025

Summary by CodeRabbit

  • Bug Fixes
    • OAuth sign-in now attempts to match and bind by email when no provider ID match exists, reducing duplicate accounts and enabling seamless sign-in to an existing account.
    • Existing provider-ID sign-in behavior unchanged; registration/binding flow unchanged when no matches are found.
    • Improved conflict detection across multiple OAuth providers.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 26, 2025

Walkthrough

The OAuth callback handler in the user module was changed to add an email-based binding step. If no user is found by the OAuth provider id, the handler now looks up a user by the OAuth-provided email; if found, it binds the OAuth id to that existing user, authenticates, and redirects to '/'. If no user is found by id or email, the previous binding/registration flow (permission checks, username validation, registration token flow) remains unchanged. Existing behavior for a direct id match is unchanged.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: fixing OAuth callback handling to support binding with email when provider ID lookup fails.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f2b79e3 and def79f0.

📒 Files selected for processing (1)
  • packages/hydrooj/src/handler/user.ts
🧰 Additional context used
🧬 Code graph analysis (1)
packages/hydrooj/src/handler/user.ts (1)
framework/framework/error.ts (1)
  • BadRequestError (49-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
packages/hydrooj/src/handler/user.ts (1)

479-484: LGTM: Initial OAuth lookup is correct.

The initial lookup by OAuth provider id followed by authentication is straightforward and correct.

Comment thread packages/hydrooj/src/handler/user.ts Outdated
Comment thread packages/hydrooj/src/handler/user.ts Outdated
Refactor OAuth binding logic to simplify account binding checks.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/hydrooj/src/handler/user.ts (1)

485-491: Email validation still missing before lookup.

The past review comment flagged that r.email might be undefined or null if the OAuth provider doesn't return an email. This concern was not addressed in the current code. Calling user.getByEmail('system', r.email) without validation could lead to unexpected behavior.

🔎 Proposed fix to add email validation
-        const udoc = await user.getByEmail('system', r.email);
-        if (udoc) {
+        const udoc = r.email ? await user.getByEmail('system', r.email) : null;
+        if (udoc) {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between def79f0 and 46b69d6.

📒 Files selected for processing (1)
  • packages/hydrooj/src/handler/user.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: build
  • GitHub Check: Analyze (javascript)

Comment on lines +485 to +491
const udoc = await user.getByEmail('system', r.email);
if (udoc) {
await this.ctx.oauth.set(args.type, r._id, udoc._id);
await successfulAuth.call(this, udoc);
this.response.redirect = '/';
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Google OAuth email verification guaranteed in access token response

💡 Result:

Short answer: No — Google does not guarantee an email_verified field in the access token response. email and email_verified are provided as claims in the ID token (or returned by userinfo when you call the UserInfo endpoint) only if you request the appropriate scope (openid email). You should verify email_verified from the ID token (or the /userinfo response), not from the access token itself. [1][2][3]

Sources:

  • OpenID Connect / email scope: ID token includes email and email_verified when you request the email scope. [1]
  • Google Sign‑in / ID token claims (example showing email_verified). [2]
  • Backend auth guide: verify the ID token (email/email_verified) or call tokeninfo/userinfo to confirm. [3]

🌐 Web query:

GitHub OAuth email verification status in API response

💡 Result:

Short answer: GitHub returns email verification status only from the Emails API (GET /user/emails) — each email object includes "verified": true|false. The standard user object (GET /user) may include an email field but does not expose a "verified" flag. OAuth tokens must include the user:email (or appropriate fine‑grained "Email addresses") scope to call the Emails endpoints. [1][2]

Sources:

  • Emails API (response includes "verified"). [1]
  • Get authenticated user (email in /user is just the public email; no verified field). [2]

🌐 Web query:

Facebook OAuth email verification guarantee

💡 Result:

Short answer: Don’t rely on Facebook Login to guarantee an app-ready, permanently verified email — verify it yourself.

Details: Facebook may return an email via OAuth when the user has one, but the docs and Facebook guidance tell apps to confirm email ownership themselves (e.g., send a verification link) because the email field isn’t always present/guaranteed for every account and practices/fields can vary. [1][2].

If you need a definitive flag you can trust for downstream account actions, send your own confirmation email (or use an IdP-aware user.profile/email_verified field in your identity provider stack). [1][3]

Sources:

  • Facebook Login guidance (recommendation to verify provider emails in your app). [1]
  • Facebook Help Center: how/when Facebook sends email confirmations and how it uses account emails. [2]
  • Notes/discussion about Graph API email behavior (email may be null/not guaranteed). [3]

🏁 Script executed:

cd packages/hydrooj/src/handler && wc -l user.ts

Repository: hydro-dev/Hydro

Length of output: 70


🏁 Script executed:

cd packages/hydrooj/src/handler && sed -n '470,510p' user.ts

Repository: hydro-dev/Hydro

Length of output: 1805


🏁 Script executed:

cd packages/hydrooj/src && find . -name "*.ts" -o -name "*.js" | xargs rg -l "oauth" | head -20

Repository: hydro-dev/Hydro

Length of output: 175


🏁 Script executed:

cd packages/hydrooj/src/handler && sed -n '430,495p' user.ts

Repository: hydro-dev/Hydro

Length of output: 2390


🏁 Script executed:

cd packages/hydrooj/src && rg -A 5 -B 5 "const udoc = await user.getByEmail" handler/user.ts

Repository: hydro-dev/Hydro

Length of output: 960


🏁 Script executed:

cd packages/hydrooj/src && cat -n model/oauth.ts | head -100

Repository: hydro-dev/Hydro

Length of output: 3092


🏁 Script executed:

cd packages/hydrooj/src && rg -l "OAuthProvider" --type ts | head -20

Repository: hydro-dev/Hydro

Length of output: 86


🏁 Script executed:

cd packages/hydrooj/src && rg "provide.*OAuthProvider\|OAuthProvider.*provide" -A 10 -B 2 | head -100

Repository: hydro-dev/Hydro

Length of output: 41


🏁 Script executed:

cd packages && find . -path "*/plugin*" -name "*.ts" | xargs rg -l "oauth" 2>/dev/null | head -10

Repository: hydro-dev/Hydro

Length of output: 86


🏁 Script executed:

cd packages/hydrooj/src && rg "OAuthProvider\|oauth.provide" -B 5 -A 10 plugin-api.ts interface.ts

Repository: hydro-dev/Hydro

Length of output: 41


🏁 Script executed:

cd packages && find . -name "*.ts" -o -name "*.js" | xargs rg "email_verified\|emailVerified" 2>/dev/null | head -30

Repository: hydro-dev/Hydro

Length of output: 41


🏁 Script executed:

cd packages && find . -path "*plugin*" -name "*.ts" -o -name "*.js" | xargs rg "provide.*oauth\|oauth.*callback" 2>/dev/null | head -50

Repository: hydro-dev/Hydro

Length of output: 41


🏁 Script executed:

cd packages/hydrooj/src && head -150 plugin-api.ts | tail -80

Repository: hydro-dev/Hydro

Length of output: 2756


🏁 Script executed:

cd packages/hydrooj/src && grep -n "oauth" plugin-api.ts interface.ts

Repository: hydro-dev/Hydro

Length of output: 401


🏁 Script executed:

cd packages && git ls-files | grep -E "plugin.*oauth|oauth.*plugin" | head -20

Repository: hydro-dev/Hydro

Length of output: 41


🏁 Script executed:

cd packages && git ls-files | grep -i plugin | head -30

Repository: hydro-dev/Hydro

Length of output: 84


🏁 Script executed:

cd packages && ls -la | grep -E "^d"

Repository: hydro-dev/Hydro

Length of output: 1583


🏁 Script executed:

cd packages && find . -maxdepth 2 -type d -name "*plugin*" -o -type d -name "*oauth*" | head -20

Repository: hydro-dev/Hydro

Length of output: 41


🏁 Script executed:

cd packages/login-with-google && find . -name "*.ts" -o -name "*.js" | head -20

Repository: hydro-dev/Hydro

Length of output: 69


🏁 Script executed:

cd packages/login-with-github && find . -name "*.ts" -o -name "*.js" | head -20

Repository: hydro-dev/Hydro

Length of output: 69


🏁 Script executed:

cd packages/login-with-google && cat -n index.ts | head -100

Repository: hydro-dev/Hydro

Length of output: 4315


🏁 Script executed:

cd packages/login-with-github && cat -n index.ts | head -150

Repository: hydro-dev/Hydro

Length of output: 5364


Validate email verification for providers that support it.

The auto-binding logic trusts the OAuth provider's email claim without verifying it has been confirmed by the provider. Google's ID token includes an email_verified claim that should be checked before auto-binding; currently, only the email field is used. GitHub's implementation correctly validates the verified flag from the Emails API. Add a check for payload.email_verified in the Google provider's callback (around login-with-google/index.ts line 59-67), or document that only providers with mandatory email verification are configured for auto-binding.

🤖 Prompt for AI Agents
In packages/hydrooj/src/handler/user.ts around lines 485 to 491, the
auto-binding flow unconditionally trusts the provider email; update the logic so
that for Google you only auto-bind when the token payload includes
email_verified === true (or otherwise assert the provider guarantees verified
emails). Specifically: inspect the OAuth payload for email_verified before
calling user.getByEmail and set the oauth mapping; if email_verified is false or
missing, skip auto-binding (or route the user to a verification/confirmation
path) and ensure a clear log or response is emitted so binding does not occur
for unverified addresses.

@undefined-moe undefined-moe merged commit cd94929 into hydro-dev:master Dec 26, 2025
8 checks passed
@renbaoshuo renbaoshuo deleted the fix/oauth-mail-binding branch December 26, 2025 16:00
Sign up for free to 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.

3 participants