mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-19 22:48:32 +00:00
feat(user): Personal access tokens can be regenerated (#38907)
Lets users regenerate a personal access token's value in place, keeping its name and scopes, instead of deleting and recreating it. Useful when a token was shared with a third party (e.g. an AI agent) and needs to be invalidated immediately without redoing scope selection. Follows the same pattern already used for OAuth2 application client secrets (`GenerateClientSecret`/`RegenerateSecret`). **Testing**: added a model unit test and a web integration test; manually verified in the running dev server that the old token stops authenticating and the new one works immediately after regenerating. <img width="1040" height="245" alt="image" src="https://github.com/user-attachments/assets/4de0d8b4-1fc4-49cf-a859-95e24d0b2c0a" /> Fixes #38683. --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
+8
-40
@@ -5,7 +5,6 @@ package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -22,7 +21,6 @@ import (
|
||||
"gitea.dev/modules/timeutil"
|
||||
"gitea.dev/modules/util"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
@@ -66,21 +64,8 @@ type ActionTask struct {
|
||||
// it only decides whether the runner is reachable, not whether the task should be killed.
|
||||
const taskReportTimeout = time.Minute
|
||||
|
||||
var successfulTokenTaskCache *lru.Cache[string, any]
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(ActionTask), func() error {
|
||||
if setting.SuccessfulTokensCacheSize > 0 {
|
||||
var err error
|
||||
successfulTokenTaskCache, err = lru.New[string, any](setting.SuccessfulTokensCacheSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to allocate Task cache: %v", err)
|
||||
}
|
||||
} else {
|
||||
successfulTokenTaskCache = nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
db.RegisterModel(new(ActionTask))
|
||||
}
|
||||
|
||||
func (task *ActionTask) Duration() time.Duration {
|
||||
@@ -195,21 +180,21 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
|
||||
}
|
||||
}
|
||||
|
||||
cacheKey := "actions:" + token
|
||||
lastEight := token[len(token)-8:]
|
||||
|
||||
if id := getTaskIDFromCache(token); id > 0 {
|
||||
if cached, _ := auth_model.TokenCache().Get(cacheKey); cached != nil {
|
||||
task := &ActionTask{
|
||||
TokenLastEight: lastEight,
|
||||
}
|
||||
// Re-get the task from the db in case it has been deleted in the intervening period
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(task)
|
||||
has, err := db.GetEngine(ctx).ID(cached.TokenID).Get(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
if has && util.CryptoConstTimeEqual(task.TokenHash, cached.TokenHash) {
|
||||
return task, nil
|
||||
}
|
||||
successfulTokenTaskCache.Remove(token)
|
||||
auth_model.TokenCache().Remove(cacheKey)
|
||||
}
|
||||
|
||||
var tasks []*ActionTask
|
||||
@@ -223,10 +208,8 @@ func GetRunningTaskByToken(ctx context.Context, token string) (*ActionTask, erro
|
||||
|
||||
for _, t := range tasks {
|
||||
tempHash := auth_model.HashToken(token, t.TokenSalt)
|
||||
if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(tempHash)) == 1 {
|
||||
if successfulTokenTaskCache != nil {
|
||||
successfulTokenTaskCache.Add(token, t.ID)
|
||||
}
|
||||
if util.CryptoConstTimeEqual(t.TokenHash, tempHash) {
|
||||
auth_model.TokenCache().Add(cacheKey, &auth_model.TokenCacheItem{TokenID: t.ID, TokenHash: t.TokenHash})
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
@@ -671,18 +654,3 @@ func logFileName(repoFullName string, taskID int64) string {
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func getTaskIDFromCache(token string) int64 {
|
||||
if successfulTokenTaskCache == nil {
|
||||
return 0
|
||||
}
|
||||
tInterface, ok := successfulTokenTaskCache.Get(token)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
t, ok := tInterface.(int64)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user