mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-24 09:38:45 +02:00
db: guard UsePreAuthKey with WHERE used=false
Add a row-level check so concurrent registrations with the same single-use key cannot both succeed. Skip the call on re-registration where the key is already marked used (#2830).
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/juanfont/headscale/hscontrol/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestCreatePreAuthKey(t *testing.T) {
|
||||
@@ -444,3 +445,45 @@ func TestMultipleLegacyKeysAllowed(t *testing.T) {
|
||||
require.Error(t, err, "duplicate non-empty prefix should be rejected")
|
||||
assert.Contains(t, err.Error(), "UNIQUE constraint failed", "should fail with UNIQUE constraint error")
|
||||
}
|
||||
|
||||
// TestUsePreAuthKeyAtomicCAS verifies that UsePreAuthKey is an atomic
|
||||
// compare-and-set: a second call against an already-used key reports
|
||||
// PAKError("authkey already used") rather than silently succeeding.
|
||||
func TestUsePreAuthKeyAtomicCAS(t *testing.T) {
|
||||
db, err := newSQLiteTestDB()
|
||||
require.NoError(t, err)
|
||||
|
||||
user, err := db.CreateUser(types.User{Name: "atomic-cas"})
|
||||
require.NoError(t, err)
|
||||
|
||||
pakNew, err := db.CreatePreAuthKey(user.TypedID(), false /* reusable */, false, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
pak, err := db.GetPreAuthKey(pakNew.Key)
|
||||
require.NoError(t, err)
|
||||
require.False(t, pak.Reusable, "test sanity: key must be single-use")
|
||||
|
||||
// First Use should commit cleanly.
|
||||
err = db.Write(func(tx *gorm.DB) error {
|
||||
return UsePreAuthKey(tx, pak)
|
||||
})
|
||||
require.NoError(t, err, "first UsePreAuthKey should succeed")
|
||||
|
||||
// Reload from disk to drop the in-memory Used=true the first call
|
||||
// set on the struct, simulating a second concurrent transaction
|
||||
// that loaded the same row before the first one committed.
|
||||
stale, err := db.GetPreAuthKey(pakNew.Key)
|
||||
require.NoError(t, err)
|
||||
|
||||
stale.Used = false
|
||||
|
||||
err = db.Write(func(tx *gorm.DB) error {
|
||||
return UsePreAuthKey(tx, stale)
|
||||
})
|
||||
require.Error(t, err, "second UsePreAuthKey on the same single-use key must fail")
|
||||
|
||||
var pakErr types.PAKError
|
||||
require.ErrorAs(t, err, &pakErr,
|
||||
"second UsePreAuthKey error must be a PAKError, got: %v", err)
|
||||
assert.Equal(t, "authkey already used", pakErr.Error())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user