Skip to content

fix: create email identity when OAuth-only user sets a password via updateUser#2425

Open
nancysangani wants to merge 1 commit intosupabase:masterfrom
nancysangani:fix/oauth-account-email-identity-on-password-update
Open

fix: create email identity when OAuth-only user sets a password via updateUser#2425
nancysangani wants to merge 1 commit intosupabase:masterfrom
nancysangani:fix/oauth-account-email-identity-on-password-update

Conversation

@nancysangani
Copy link

@nancysangani nancysangani commented Mar 13, 2026

Fixes #2320

Problem

When updateUser is called with a password on an OAuth-only account, no email identity was created in auth.identities. This meant the user could not sign in with email+password even though the password was saved correctly on the users row.

Fix

Inside the password-update transaction in UserUpdate, after UpdatePassword succeeds, check whether the user already has an email identity. If not (i.e. OAuth-only account), create one using the user's existing confirmed email and call UpdateAppMetaDataProviders so that app_metadata.providers includes "email".

The fix is no-op for users who already have an email identity.

Changes

  • internal/api/user.go

@nancysangani nancysangani requested a review from a team as a code owner March 13, 2026 15:18
Copilot AI review requested due to automatic review settings March 13, 2026 15:18
@nancysangani nancysangani force-pushed the fix/oauth-account-email-identity-on-password-update branch from 7d16440 to f077d13 Compare March 13, 2026 15:21
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a dedicated rate limit for sign-in/sign-up flows, adjusts /token rate-limiting behavior by grant type, and adds a safeguard during password updates to ensure an email identity exists when needed.

Changes:

  • Add a new global config setting for sign-in/sign-up rate limiting.
  • Introduce a SignIns limiter and apply it to /token grant_type=password while keeping other grants on existing limiters.
  • When updating a password, create an email identity if missing and update app_metadata.providers.

Reviewed changes

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

File Description
internal/conf/configuration.go Adds RateLimitSignInSignUps config field used for sign-in/sign-up limiter sizing.
internal/api/options.go Adds SignIns limiter and switches Signups to use the new sign-in/sign-up rate limit value.
internal/api/token.go Changes rate limiter selection so password grant uses SignIns, other grants use existing limiters.
internal/api/user.go Creates an email identity on password update when the user has an email but lacks an email identity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +228 to +246
if user.GetEmail() != "" {
if _, terr = models.FindIdentityByIdAndProvider(tx, user.ID.String(), "email"); terr != nil {
if !models.IsNotFoundError(terr) {
return apierrors.NewInternalServerError("Error finding email identity").WithInternalError(terr)
}
emailIdentity, terr := models.NewIdentity(user, "email", map[string]interface{}{
"sub": user.ID.String(),
"email": user.GetEmail(),
})
if terr != nil {
return apierrors.NewInternalServerError("Error creating email identity").WithInternalError(terr)
}
if terr := tx.Create(emailIdentity); terr != nil {
return apierrors.NewInternalServerError("Error saving email identity").WithInternalError(terr)
}
if terr := user.UpdateAppMetaDataProviders(tx); terr != nil {
return apierrors.NewInternalServerError("Error updating providers").WithInternalError(terr)
}
}
@nancysangani
Copy link
Author

/cc @staaldraad
/cc @fadymak

Comment on lines +228 to +233
if user.GetEmail() != "" {
if _, terr = models.FindIdentityByIdAndProvider(tx, user.ID.String(), "email"); terr != nil {
if !models.IsNotFoundError(terr) {
return apierrors.NewInternalServerError("Error finding email identity").WithInternalError(terr)
}
emailIdentity, terr := models.NewIdentity(user, "email", map[string]interface{}{
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 Severity: MEDIUM

The code creates an email identity without verifying if the user's email is confirmed. This could allow users with unconfirmed emails from OAuth providers to gain email/password login capabilities without verification. A check for user.EmailConfirmedAt != nil should be added.
Helpful? Add 👍 / 👎

💡 Fix Suggestion

Suggestion: Add a check for user.EmailConfirmedAt != nil alongside the existing user.GetEmail() != "" guard before creating the email identity. This ensures that only users with a verified/confirmed email address can have an email identity created for them. Without this check, an OAuth user whose email was never confirmed could obtain an email+password login capability for an unverified email address.

⚠️ Experimental Feature: This code suggestion is automatically generated. Please review carefully.

Suggested change
if user.GetEmail() != "" {
if _, terr = models.FindIdentityByIdAndProvider(tx, user.ID.String(), "email"); terr != nil {
if !models.IsNotFoundError(terr) {
return apierrors.NewInternalServerError("Error finding email identity").WithInternalError(terr)
}
emailIdentity, terr := models.NewIdentity(user, "email", map[string]interface{}{
if user.GetEmail() != "" && user.EmailConfirmedAt != nil {

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.

Calling updateUser on a oAuth only account doesn't enable email provider

2 participants