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>
This commit is contained in:
ToastyTheBot
2026-08-27 03:32:44 +08:00
committed by GitHub
parent 3c4d5a6a5c
commit 646ea0f253
76 changed files with 1594 additions and 831 deletions
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package deploykey
import (
"context"
"gitea.dev/models/asymkey"
"gitea.dev/models/db"
"gitea.dev/models/perm"
"gitea.dev/modules/util"
"xorm.io/builder"
)
// AddDeployKeySSH add new deploy-key to database and authorized_keys file.
func AddDeployKeySSH(ctx context.Context, repoID int64, name, content string, accessMode perm.AccessMode) (*DeployKey, error) {
if accessMode != perm.AccessModeRead && accessMode != perm.AccessModeWrite {
return nil, util.NewInvalidArgumentErrorf("invalid access mode")
}
return db.WithTx2(ctx, func(ctx context.Context) (*DeployKey, error) {
pkey, err := asymkey.FindOrAddDeployPublicKey(ctx, content)
if err != nil {
return nil, err
}
if has, err := db.Exist[DeployKey](ctx, builder.Eq{"repo_id": repoID, "key_id": pkey.ID}); err != nil {
return nil, err
} else if has {
return nil, ErrDeployKeyAlreadyExist{pkey.ID, repoID}
}
if err := checkDeployKeyName(ctx, repoID, name); err != nil {
return nil, err
}
key := &DeployKey{KeyID: pkey.ID, RepoID: repoID, KeyType: KeyTypeSSH, Name: name, Fingerprint: pkey.Fingerprint, Mode: accessMode}
return key, db.Insert(ctx, key)
})
}
func (key *DeployKey) LoadPublicKey(ctx context.Context) (err error) {
if key.PublicKey != nil {
return nil
}
key.PublicKey, err = asymkey.GetPublicKeyByID(ctx, key.KeyID)
return err
}
// GetDeployKeyByID returns deploy-key by given ID.
func GetDeployKeyByID(ctx context.Context, repoID, deployKeyID int64) (*DeployKey, error) {
key, exist, err := db.Get[DeployKey](ctx, builder.Eq{"id": deployKeyID, "repo_id": repoID})
if err != nil {
return nil, err
} else if !exist {
return nil, ErrDeployKeyNotExist{deployKeyID, 0, repoID}
}
return key, nil
}
// GetDeployKeyByRepoPublicKey returns deploy-key by given public key ID and repository ID.
func GetDeployKeyByRepoPublicKey(ctx context.Context, repoID, publicKeyID int64) (*DeployKey, error) {
// the type is part of the condition because every token row carries key id 0
key, exist, err := db.Get[DeployKey](ctx, builder.Eq{"key_id": publicKeyID, "repo_id": repoID, "key_type": KeyTypeSSH})
if err != nil {
return nil, err
} else if !exist {
return nil, ErrDeployKeyNotExist{0, publicKeyID, repoID}
}
return key, nil
}
// IsDeployKeyExistByPublicKeyID return true if there is at least one deploy-key with the key id
func IsDeployKeyExistByPublicKeyID(ctx context.Context, keyID int64) (bool, error) {
return db.Exist[DeployKey](ctx, builder.Eq{"key_id": keyID, "key_type": KeyTypeSSH})
}