feat(oauth): migrate GitHub user identification from login to numeric ID

This commit is contained in:
CaIon
2026-02-05 20:30:48 +08:00
parent 7801dc6762
commit 424595e620
3 changed files with 50 additions and 7 deletions

View File

@@ -1,6 +1,7 @@
package controller
import (
"fmt"
"net/http"
"strconv"
@@ -147,11 +148,18 @@ func handleOAuthBind(c *gin.Context, provider oauth.Provider) {
return
}
// Check if this OAuth account is already bound
// Check if this OAuth account is already bound (check both new ID and legacy ID)
if provider.IsUserIDTaken(oauthUser.ProviderUserID) {
common.ApiErrorI18n(c, i18n.MsgOAuthAlreadyBound, providerParams(provider.GetName()))
return
}
// Also check legacy ID to prevent duplicate bindings during migration period
if legacyID, ok := oauthUser.Extra["legacy_id"].(string); ok && legacyID != "" {
if provider.IsUserIDTaken(legacyID) {
common.ApiErrorI18n(c, i18n.MsgOAuthAlreadyBound, providerParams(provider.GetName()))
return
}
}
// Get current user from session
session := sessions.Default(c)
@@ -178,7 +186,7 @@ func handleOAuthBind(c *gin.Context, provider oauth.Provider) {
func findOrCreateOAuthUser(c *gin.Context, provider oauth.Provider, oauthUser *oauth.OAuthUser, session sessions.Session) (*model.User, error) {
user := &model.User{}
// Check if user already exists
// Check if user already exists with new ID
if provider.IsUserIDTaken(oauthUser.ProviderUserID) {
provider.SetProviderUserID(user, oauthUser.ProviderUserID)
err := provider.FillUserByProviderID(user, oauthUser.ProviderUserID)
@@ -192,6 +200,27 @@ func findOrCreateOAuthUser(c *gin.Context, provider oauth.Provider, oauthUser *o
return user, nil
}
// Try to find user with legacy ID (for GitHub migration from login to numeric ID)
if legacyID, ok := oauthUser.Extra["legacy_id"].(string); ok && legacyID != "" {
if provider.IsUserIDTaken(legacyID) {
provider.SetProviderUserID(user, legacyID)
err := provider.FillUserByProviderID(user, legacyID)
if err != nil {
return nil, err
}
if user.Id != 0 {
// Found user with legacy ID, migrate to new ID
common.SysLog(fmt.Sprintf("[OAuth] Migrating user %d from legacy_id=%s to new_id=%s",
user.Id, legacyID, oauthUser.ProviderUserID))
if err := user.UpdateGitHubId(oauthUser.ProviderUserID); err != nil {
common.SysError(fmt.Sprintf("[OAuth] Failed to migrate user %d: %s", user.Id, err.Error()))
// Continue with login even if migration fails
}
return user, nil
}
}
}
// User doesn't exist, create new user if registration is enabled
if !common.RegisterEnabled {
return nil, &OAuthRegistrationDisabledError{}

View File

@@ -540,6 +540,14 @@ func (user *User) FillUserByGitHubId() error {
return nil
}
// UpdateGitHubId updates the user's GitHub ID (used for migration from login to numeric ID)
func (user *User) UpdateGitHubId(newGitHubId string) error {
if user.Id == 0 {
return errors.New("user id is empty")
}
return DB.Model(user).Update("github_id", newGitHubId).Error
}
func (user *User) FillUserByDiscordId() error {
if user.DiscordId == "" {
return errors.New("discord id 为空!")

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/QuantumNous/new-api/common"
@@ -29,7 +30,8 @@ type gitHubOAuthResponse struct {
}
type gitHubUser struct {
Login string `json:"login"`
Id int64 `json:"id"` // GitHub numeric ID (permanent, never changes)
Login string `json:"login"` // GitHub username (can be changed by user)
Name string `json:"name"`
Email string `json:"email"`
}
@@ -127,18 +129,22 @@ func (p *GitHubProvider) GetUserInfo(ctx context.Context, token *OAuthToken) (*O
return nil, err
}
if githubUser.Login == "" {
logger.LogError(ctx, "[OAuth-GitHub] GetUserInfo failed: empty login field")
if githubUser.Id == 0 || githubUser.Login == "" {
logger.LogError(ctx, "[OAuth-GitHub] GetUserInfo failed: empty id or login field")
return nil, NewOAuthError(i18n.MsgOAuthUserInfoEmpty, map[string]any{"Provider": "GitHub"})
}
logger.LogDebug(ctx, "[OAuth-GitHub] GetUserInfo success: login=%s, name=%s, email=%s", githubUser.Login, githubUser.Name, githubUser.Email)
logger.LogDebug(ctx, "[OAuth-GitHub] GetUserInfo success: id=%d, login=%s, name=%s, email=%s",
githubUser.Id, githubUser.Login, githubUser.Name, githubUser.Email)
return &OAuthUser{
ProviderUserID: githubUser.Login,
ProviderUserID: strconv.FormatInt(githubUser.Id, 10), // Use numeric ID as primary identifier
Username: githubUser.Login,
DisplayName: githubUser.Name,
Email: githubUser.Email,
Extra: map[string]any{
"legacy_id": githubUser.Login, // Store login for migration from old accounts
},
}, nil
}