feat(oauth): migrate GitHub user identification from login to numeric ID
This commit is contained in:
@@ -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{}
|
||||
|
||||
@@ -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 为空!")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user