mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-05-06 20:41:38 +00:00
feat(oauth): migrate GitHub user identification from login to numeric ID
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -147,11 +148,18 @@ func handleOAuthBind(c *gin.Context, provider oauth.Provider) {
|
|||||||
return
|
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) {
|
if provider.IsUserIDTaken(oauthUser.ProviderUserID) {
|
||||||
common.ApiErrorI18n(c, i18n.MsgOAuthAlreadyBound, providerParams(provider.GetName()))
|
common.ApiErrorI18n(c, i18n.MsgOAuthAlreadyBound, providerParams(provider.GetName()))
|
||||||
return
|
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
|
// Get current user from session
|
||||||
session := sessions.Default(c)
|
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) {
|
func findOrCreateOAuthUser(c *gin.Context, provider oauth.Provider, oauthUser *oauth.OAuthUser, session sessions.Session) (*model.User, error) {
|
||||||
user := &model.User{}
|
user := &model.User{}
|
||||||
|
|
||||||
// Check if user already exists
|
// Check if user already exists with new ID
|
||||||
if provider.IsUserIDTaken(oauthUser.ProviderUserID) {
|
if provider.IsUserIDTaken(oauthUser.ProviderUserID) {
|
||||||
provider.SetProviderUserID(user, oauthUser.ProviderUserID)
|
provider.SetProviderUserID(user, oauthUser.ProviderUserID)
|
||||||
err := provider.FillUserByProviderID(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
|
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
|
// User doesn't exist, create new user if registration is enabled
|
||||||
if !common.RegisterEnabled {
|
if !common.RegisterEnabled {
|
||||||
return nil, &OAuthRegistrationDisabledError{}
|
return nil, &OAuthRegistrationDisabledError{}
|
||||||
|
|||||||
@@ -540,6 +540,14 @@ func (user *User) FillUserByGitHubId() error {
|
|||||||
return nil
|
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 {
|
func (user *User) FillUserByDiscordId() error {
|
||||||
if user.DiscordId == "" {
|
if user.DiscordId == "" {
|
||||||
return errors.New("discord id 为空!")
|
return errors.New("discord id 为空!")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/QuantumNous/new-api/common"
|
"github.com/QuantumNous/new-api/common"
|
||||||
@@ -29,7 +30,8 @@ type gitHubOAuthResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type gitHubUser 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"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
@@ -127,18 +129,22 @@ func (p *GitHubProvider) GetUserInfo(ctx context.Context, token *OAuthToken) (*O
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if githubUser.Login == "" {
|
if githubUser.Id == 0 || githubUser.Login == "" {
|
||||||
logger.LogError(ctx, "[OAuth-GitHub] GetUserInfo failed: empty login field")
|
logger.LogError(ctx, "[OAuth-GitHub] GetUserInfo failed: empty id or login field")
|
||||||
return nil, NewOAuthError(i18n.MsgOAuthUserInfoEmpty, map[string]any{"Provider": "GitHub"})
|
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{
|
return &OAuthUser{
|
||||||
ProviderUserID: githubUser.Login,
|
ProviderUserID: strconv.FormatInt(githubUser.Id, 10), // Use numeric ID as primary identifier
|
||||||
Username: githubUser.Login,
|
Username: githubUser.Login,
|
||||||
DisplayName: githubUser.Name,
|
DisplayName: githubUser.Name,
|
||||||
Email: githubUser.Email,
|
Email: githubUser.Email,
|
||||||
|
Extra: map[string]any{
|
||||||
|
"legacy_id": githubUser.Login, // Store login for migration from old accounts
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user