This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 32eafc80b fix(github): made token expiration fields nullable and
updated related logic (#8707)
32eafc80b is described below
commit 32eafc80b8cfcaedeaef5273e777e5d37763e938
Author: Yuvraj Singh Chauhan <[email protected]>
AuthorDate: Thu Feb 12 20:23:07 2026 +0530
fix(github): made token expiration fields nullable and updated related
logic (#8707)
---
backend/plugins/github/models/connection.go | 8 ++--
...dify_connection_token_expires_at_to_nullable.go | 52 ++++++++++++++++++++++
.../github/models/migrationscripts/register.go | 1 +
backend/plugins/github/token/round_tripper_test.go | 3 +-
backend/plugins/github/token/token_provider.go | 12 +++--
.../plugins/github/token/token_provider_test.go | 18 +++++---
6 files changed, 80 insertions(+), 14 deletions(-)
diff --git a/backend/plugins/github/models/connection.go
b/backend/plugins/github/models/connection.go
index 4dba2f756..d03353c18 100644
--- a/backend/plugins/github/models/connection.go
+++ b/backend/plugins/github/models/connection.go
@@ -56,13 +56,13 @@ type GithubConn struct {
helper.MultiAuth `mapstructure:",squash"`
GithubAccessToken `mapstructure:",squash" authMethod:"AccessToken"`
GithubAppKey `mapstructure:",squash" authMethod:"AppKey"`
- RefreshToken string `mapstructure:"refreshToken"
json:"refreshToken" gorm:"type:text;serializer:encdec"`
- TokenExpiresAt time.Time `mapstructure:"tokenExpiresAt"
json:"tokenExpiresAt"`
- RefreshTokenExpiresAt time.Time `mapstructure:"refreshTokenExpiresAt"
json:"refreshTokenExpiresAt"`
+ RefreshToken string `mapstructure:"refreshToken"
json:"refreshToken" gorm:"type:text;serializer:encdec"`
+ TokenExpiresAt *time.Time `mapstructure:"tokenExpiresAt"
json:"tokenExpiresAt"`
+ RefreshTokenExpiresAt *time.Time `mapstructure:"refreshTokenExpiresAt"
json:"refreshTokenExpiresAt"`
}
// UpdateToken updates the token and refresh token information
-func (conn *GithubConn) UpdateToken(newToken, newRefreshToken string, expiry,
refreshExpiry time.Time) {
+func (conn *GithubConn) UpdateToken(newToken, newRefreshToken string, expiry,
refreshExpiry *time.Time) {
conn.Token = newToken
conn.RefreshToken = newRefreshToken
conn.TokenExpiresAt = expiry
diff --git
a/backend/plugins/github/models/migrationscripts/20260211_modify_connection_token_expires_at_to_nullable.go
b/backend/plugins/github/models/migrationscripts/20260211_modify_connection_token_expires_at_to_nullable.go
new file mode 100644
index 000000000..ff7b127fb
--- /dev/null
+++
b/backend/plugins/github/models/migrationscripts/20260211_modify_connection_token_expires_at_to_nullable.go
@@ -0,0 +1,52 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+ "time"
+
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+ "github.com/apache/incubator-devlake/helpers/migrationhelper"
+)
+
+type githubConnection20260211 struct {
+ TokenExpiresAt *time.Time
+ RefreshTokenExpiresAt *time.Time
+}
+
+func (githubConnection20260211) TableName() string {
+ return "_tool_github_connections"
+}
+
+type modifyTokenExpiresAtToNullable struct{}
+
+func (*modifyTokenExpiresAtToNullable) Up(basicRes context.BasicRes)
errors.Error {
+ return migrationhelper.AutoMigrateTables(
+ basicRes,
+ &githubConnection20260211{},
+ )
+}
+
+func (*modifyTokenExpiresAtToNullable) Version() uint64 {
+ return 20260211000001
+}
+
+func (*modifyTokenExpiresAtToNullable) Name() string {
+ return "modify token_expires_at and refresh_token_expires_at to
nullable"
+}
diff --git a/backend/plugins/github/models/migrationscripts/register.go
b/backend/plugins/github/models/migrationscripts/register.go
index 74f9d712b..009497289 100644
--- a/backend/plugins/github/models/migrationscripts/register.go
+++ b/backend/plugins/github/models/migrationscripts/register.go
@@ -56,5 +56,6 @@ func All() []plugin.MigrationScript {
new(changeIssueComponentType),
new(addIndexToGithubJobs),
new(addRefreshTokenFields),
+ new(modifyTokenExpiresAtToNullable),
}
}
diff --git a/backend/plugins/github/token/round_tripper_test.go
b/backend/plugins/github/token/round_tripper_test.go
index 6767d8ccf..e766adb88 100644
--- a/backend/plugins/github/token/round_tripper_test.go
+++ b/backend/plugins/github/token/round_tripper_test.go
@@ -36,6 +36,7 @@ func TestRoundTripper401Refresh(t *testing.T) {
mockRT := new(MockRoundTripper)
client := &http.Client{Transport: mockRT}
+ expiry := time.Now().Add(10 * time.Minute) // Not expired
conn := &models.GithubConnection{
GithubConn: models.GithubConn{
RefreshToken: "refresh_token",
@@ -44,7 +45,7 @@ func TestRoundTripper401Refresh(t *testing.T) {
Token: "old_token",
},
},
- TokenExpiresAt: time.Now().Add(10 * time.Minute), //
Not expired
+ TokenExpiresAt: &expiry,
GithubAppKey: models.GithubAppKey{
AppKey: api.AppKey{
AppId: "123",
diff --git a/backend/plugins/github/token/token_provider.go
b/backend/plugins/github/token/token_provider.go
index ba9941cd4..ed3fa2256 100644
--- a/backend/plugins/github/token/token_provider.go
+++ b/backend/plugins/github/token/token_provider.go
@@ -84,7 +84,10 @@ func (tp *TokenProvider) needsRefresh() bool {
}
}
- return time.Now().Add(buffer).After(tp.conn.TokenExpiresAt)
+ if tp.conn.TokenExpiresAt == nil {
+ return false
+ }
+ return time.Now().Add(buffer).After(*tp.conn.TokenExpiresAt)
}
func (tp *TokenProvider) refreshToken() errors.Error {
@@ -145,11 +148,14 @@ func (tp *TokenProvider) refreshToken() errors.Error {
return errors.Default.New(fmt.Sprintf("empty access token
returned; response body: %s", bodyStr))
}
+ tokenExpiredAt := time.Now().Add(time.Duration(result.ExpiresIn) *
time.Second)
+ refreshTokenExpiredAt :=
time.Now().Add(time.Duration(result.RefreshTokenExpiresIn) * time.Second)
+
tp.conn.UpdateToken(
result.AccessToken,
result.RefreshToken,
- time.Now().Add(time.Duration(result.ExpiresIn)*time.Second),
-
time.Now().Add(time.Duration(result.RefreshTokenExpiresIn)*time.Second),
+ &tokenExpiredAt,
+ &refreshTokenExpiredAt,
)
if tp.dal != nil {
diff --git a/backend/plugins/github/token/token_provider_test.go
b/backend/plugins/github/token/token_provider_test.go
index 1c296376a..3319f5591 100644
--- a/backend/plugins/github/token/token_provider_test.go
+++ b/backend/plugins/github/token/token_provider_test.go
@@ -55,15 +55,18 @@ func TestNeedsRefresh(t *testing.T) {
}
// Not expired, outside buffer
- tp.conn.TokenExpiresAt = time.Now().Add(10 * time.Minute)
+ expiry1 := time.Now().Add(10 * time.Minute)
+ tp.conn.TokenExpiresAt = &expiry1
assert.False(t, tp.needsRefresh())
// Inside buffer
- tp.conn.TokenExpiresAt = time.Now().Add(1 * time.Minute)
+ expiry2 := time.Now().Add(1 * time.Minute)
+ tp.conn.TokenExpiresAt = &expiry2
assert.True(t, tp.needsRefresh())
// Expired
- tp.conn.TokenExpiresAt = time.Now().Add(-1 * time.Minute)
+ expiry3 := time.Now().Add(-1 * time.Minute)
+ tp.conn.TokenExpiresAt = &expiry3
assert.True(t, tp.needsRefresh())
// No refresh token
@@ -75,10 +78,11 @@ func TestTokenProviderConcurrency(t *testing.T) {
mockRT := new(MockRoundTripper)
client := &http.Client{Transport: mockRT}
+ expired := time.Now().Add(-1 * time.Minute) // Expired
conn := &models.GithubConnection{
GithubConn: models.GithubConn{
RefreshToken: "refresh_token",
- TokenExpiresAt: time.Now().Add(-1 * time.Minute), //
Expired
+ TokenExpiresAt: &expired,
GithubAppKey: models.GithubAppKey{
AppKey: api.AppKey{
AppId: "123",
@@ -129,11 +133,13 @@ func TestConfigurableBuffer(t *testing.T) {
}
// 9 minutes remaining (inside 10m buffer)
- tp.conn.TokenExpiresAt = time.Now().Add(9 * time.Minute)
+ expiry9 := time.Now().Add(9 * time.Minute)
+ tp.conn.TokenExpiresAt = &expiry9
assert.True(t, tp.needsRefresh())
// 11 minutes remaining (outside 10m buffer)
- tp.conn.TokenExpiresAt = time.Now().Add(11 * time.Minute)
+ expiry11 := time.Now().Add(11 * time.Minute)
+ tp.conn.TokenExpiresAt = &expiry11
assert.False(t, tp.needsRefresh())
}