Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
424595e6
authored
Feb 05, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(oauth): migrate GitHub user identification from login to numeric ID
parent
7801dc67
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
50 additions
and
7 deletions
+50
-7
controller/oauth.go
+31
-2
model/user.go
+8
-0
oauth/github.go
+11
-5
No files found.
controller/oauth.go
View file @
424595e6
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
{}
...
...
model/user.go
View file @
424595e6
...
...
@@ -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 为空!"
)
...
...
oauth/github.go
View file @
424595e6
...
...
@@ -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
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment