Files
Gitea/models/deploykey/deploykey_token.go
T
ToastyTheBot 646ea0f253 feat: add deploy tokens (#37306)
Deploy keys only work over SSH. A deploy token is their counterpart for HTTPS: a repository scoped credential, used as the password of a Git request, with read or read and write access. It covers Git operations and LFS, and can be regenerated in place.

Signed-off-by: silverwind <me@silverwind.io>
Co-authored-by: Claude Mythos <noreply@anthropic.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-08-26 19:32:44 +00:00

77 lines
2.3 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package deploykey
import (
"context"
"strings"
"gitea.dev/models/db"
"gitea.dev/models/perm"
"gitea.dev/modules/base"
"gitea.dev/modules/util"
"xorm.io/builder"
)
const (
DeployTokenPrefix = "gdt_" // lets a secret scanner recognize a leaked token
deployTokenLength = 43 // 256 bits of entropy over the 62 alphanumerical characters
)
func (key *DeployKey) generateToken() {
key.Token = DeployTokenPrefix + util.CryptoRandomString(deployTokenLength)
key.TokenHash = base.EncodeSha256(key.Token)
key.Fingerprint = key.Token[:len(DeployTokenPrefix)+2] + "********" + key.Token[len(key.Token)-2:]
}
// AddDeployKeyToken adds a token that authenticates git HTTP requests for one repository.
// The plaintext token is only readable on the returned key.
func AddDeployKeyToken(ctx context.Context, repoID int64, name string, accessMode perm.AccessMode) (*DeployKey, error) {
key := &DeployKey{
RepoID: repoID,
KeyType: KeyTypeToken,
Name: name,
Mode: accessMode,
}
key.generateToken()
return db.WithTx2(ctx, func(ctx context.Context) (*DeployKey, error) {
if err := checkDeployKeyName(ctx, repoID, name); err != nil {
return nil, err
}
return key, db.Insert(ctx, key)
})
}
// RegenerateDeployKeyToken replaces the token value of an existing deploy token, keeping its name and access mode.
func RegenerateDeployKeyToken(ctx context.Context, repoID, keyID int64) (*DeployKey, error) {
key, err := GetDeployKeyByID(ctx, repoID, keyID)
if err != nil {
return nil, err
}
if key.KeyType != KeyTypeToken {
return nil, ErrDeployKeyNotExist{keyID, 0, repoID}
}
key.generateToken()
_, err = db.GetEngine(ctx).ID(key.ID).Cols("token_hash", "fingerprint").NoAutoTime().Update(key)
return key, err
}
// VerifyDeployKeyToken returns the deploy-key which the given plaintext token authenticates.
func VerifyDeployKeyToken(ctx context.Context, token string) (*DeployKey, error) {
if !strings.HasPrefix(token, DeployTokenPrefix) { // spares a query for every password of a normal user
return nil, ErrDeployKeyNotExist{}
}
key, exist, err := db.Get[DeployKey](ctx, builder.Eq{"token_hash": base.EncodeSha256(token), "key_type": KeyTypeToken})
if err != nil {
return nil, err
} else if !exist {
return nil, ErrDeployKeyNotExist{}
}
return key, nil
}