0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-06-22 22:58:50 +02:00

switch to use user_settings table per feedback

This commit is contained in:
techknowlogick 2025-08-08 18:54:55 -04:00
parent 2f3f2d1afd
commit 3ed601dcf4
5 changed files with 74 additions and 51 deletions

View File

@ -386,7 +386,6 @@ func prepareMigrationTasks() []*migration {
// Gitea 1.24.0 ends at migration ID number 320 (database version 321)
newMigration(321, "Use LONGTEXT for some columns and fix review_state.updated_files column", v1_25.UseLongTextInSomeColumnsAndFixBugs),
newMigration(322, "Add Mirror SSH keypair table", v1_25.AddUserSSHKeypairTable),
}
return preparedMigrations
}

View File

@ -1,24 +0,0 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package v1_25
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
)
func AddUserSSHKeypairTable(x *xorm.Engine) error {
type UserSSHKeypair struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
PrivateKeyEncrypted string `xorm:"TEXT NOT NULL"`
PublicKey string `xorm:"TEXT NOT NULL"`
Fingerprint string `xorm:"VARCHAR(255) UNIQUE NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
}
return x.Sync(new(UserSSHKeypair))
}

View File

@ -16,7 +16,6 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/secret"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"golang.org/x/crypto/ssh"
@ -24,30 +23,44 @@ import (
// UserSSHKeypair represents an SSH keypair for repository mirroring
type UserSSHKeypair struct {
ID int64 `xorm:"pk autoincr"`
OwnerID int64 `xorm:"INDEX NOT NULL"`
PrivateKeyEncrypted string `xorm:"TEXT NOT NULL"`
PublicKey string `xorm:"TEXT NOT NULL"`
Fingerprint string `xorm:"VARCHAR(255) UNIQUE NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
UpdatedUnix timeutil.TimeStamp `xorm:"updated"`
OwnerID int64
PrivateKeyEncrypted string
PublicKey string
Fingerprint string
}
func init() {
db.RegisterModel(new(UserSSHKeypair))
}
// GetUserSSHKeypairByOwner gets the most recent SSH keypair for the given owner
// GetUserSSHKeypairByOwner gets the SSH keypair for the given owner
func GetUserSSHKeypairByOwner(ctx context.Context, ownerID int64) (*UserSSHKeypair, error) {
keypair := &UserSSHKeypair{}
has, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).
Desc("created_unix").Get(keypair)
settings, err := user_model.GetSettings(ctx, ownerID, []string{
user_model.UserSSHMirrorPrivPem,
user_model.UserSSHMirrorPubPem,
user_model.UserSSHMirrorFingerprint,
})
if err != nil {
return nil, err
}
if !has {
if len(settings) == 0 {
return nil, util.NewNotExistErrorf("SSH keypair does not exist for owner %d", ownerID)
}
keypair := &UserSSHKeypair{
OwnerID: ownerID,
}
if privSetting, exists := settings[user_model.UserSSHMirrorPrivPem]; exists {
keypair.PrivateKeyEncrypted = privSetting.SettingValue
}
if pubSetting, exists := settings[user_model.UserSSHMirrorPubPem]; exists {
keypair.PublicKey = pubSetting.SettingValue
}
if fpSetting, exists := settings[user_model.UserSSHMirrorFingerprint]; exists {
keypair.Fingerprint = fpSetting.SettingValue
}
if keypair.PrivateKeyEncrypted == "" || keypair.PublicKey == "" || keypair.Fingerprint == "" {
return nil, util.NewNotExistErrorf("SSH keypair incomplete for owner %d", ownerID)
}
return keypair, nil
}
@ -73,6 +86,22 @@ func CreateUserSSHKeypair(ctx context.Context, ownerID int64) (*UserSSHKeypair,
return nil, fmt.Errorf("failed to encrypt private key: %w", err)
}
err = db.WithTx(ctx, func(ctx context.Context) error {
if err := user_model.SetUserSetting(ctx, ownerID, user_model.UserSSHMirrorPrivPem, privateKeyEncrypted); err != nil {
return fmt.Errorf("failed to save private key: %w", err)
}
if err := user_model.SetUserSetting(ctx, ownerID, user_model.UserSSHMirrorPubPem, publicKeyStr); err != nil {
return fmt.Errorf("failed to save public key: %w", err)
}
if err := user_model.SetUserSetting(ctx, ownerID, user_model.UserSSHMirrorFingerprint, fingerprintStr); err != nil {
return fmt.Errorf("failed to save fingerprint: %w", err)
}
return nil
})
if err != nil {
return nil, err
}
keypair := &UserSSHKeypair{
OwnerID: ownerID,
PrivateKeyEncrypted: privateKeyEncrypted,
@ -80,7 +109,7 @@ func CreateUserSSHKeypair(ctx context.Context, ownerID int64) (*UserSSHKeypair,
Fingerprint: fingerprintStr,
}
return keypair, db.Insert(ctx, keypair)
return keypair, nil
}
// GetDecryptedPrivateKey returns the decrypted private key
@ -115,12 +144,29 @@ func (k *UserSSHKeypair) GetPublicKeyWithComment(ctx context.Context) (string, e
// DeleteUserSSHKeypair deletes an SSH keypair
func DeleteUserSSHKeypair(ctx context.Context, ownerID int64) error {
_, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Delete(&UserSSHKeypair{})
return err
return db.WithTx(ctx, func(ctx context.Context) error {
if err := user_model.DeleteUserSetting(ctx, ownerID, user_model.UserSSHMirrorPrivPem); err != nil {
return err
}
if err := user_model.DeleteUserSetting(ctx, ownerID, user_model.UserSSHMirrorPubPem); err != nil {
return err
}
return user_model.DeleteUserSetting(ctx, ownerID, user_model.UserSSHMirrorFingerprint)
})
}
// RegenerateUserSSHKeypair regenerates an SSH keypair for the given owner
func RegenerateUserSSHKeypair(ctx context.Context, ownerID int64) (*UserSSHKeypair, error) {
// TODO: This creates a new one old ones will be garbage collected later, as the user may accidentally regenerate
return CreateUserSSHKeypair(ctx, ownerID)
var keypair *UserSSHKeypair
err := db.WithTx(ctx, func(ctx context.Context) error {
_ = DeleteUserSSHKeypair(ctx, ownerID)
newKeypair, err := CreateUserSSHKeypair(ctx, ownerID)
if err != nil {
return err
}
keypair = newKeypair
return nil
})
return keypair, err
}

View File

@ -29,8 +29,6 @@ func TestUserSSHKeypair(t *testing.T) {
assert.NotEmpty(t, keypair.PublicKey)
assert.NotEmpty(t, keypair.PrivateKeyEncrypted)
assert.NotEmpty(t, keypair.Fingerprint)
assert.Positive(t, keypair.CreatedUnix)
assert.Positive(t, keypair.UpdatedUnix)
// Verify the public key is in SSH format
assert.Contains(t, keypair.PublicKey, "ssh-ed25519")
@ -54,7 +52,7 @@ func TestUserSSHKeypair(t *testing.T) {
// Test retrieving the keypair
retrieved, err := repo_model.GetUserSSHKeypairByOwner(db.DefaultContext, 3)
require.NoError(t, err)
assert.Equal(t, created.ID, retrieved.ID)
assert.Equal(t, created.OwnerID, retrieved.OwnerID)
assert.Equal(t, created.PublicKey, retrieved.PublicKey)
assert.Equal(t, created.Fingerprint, retrieved.Fingerprint)
@ -128,7 +126,7 @@ func TestUserSSHKeypairConcurrency(t *testing.T) {
// Test concurrent creation of keypairs to ensure no race conditions
t.Run("ConcurrentCreation", func(t *testing.T) {
ctx := t.Context()
ctx := db.DefaultContext
results := make(chan error, 10)
// Start multiple goroutines creating keypairs for different owners

View File

@ -26,4 +26,8 @@ const (
SettingEmailNotificationGiteaActionsAll = "all"
SettingEmailNotificationGiteaActionsFailureOnly = "failure-only" // Default for actions email preference
SettingEmailNotificationGiteaActionsDisabled = "disabled"
UserSSHMirrorPrivPem = "ssh_mirror.priv_pem"
UserSSHMirrorPubPem = "ssh_mirror.pub_pem"
UserSSHMirrorFingerprint = "ssh_mirror.fingerprint"
)