mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(coderd/database): add AI Gateway key auth lookup and last-used queries (#26505)
Adds DB methods`GetAIGatewayKeyIDByHashedSecret` and `UpdateAIGatewayKeyLastUsedAt`. `GetAIGatewayKeyIDByHashedSecret` - returns AI Gateway key ID by hashed secret value. `UpdateAIGatewayKeyLastUsedAt` - updates last used timestamp for given AI Gateway key. Used by standalone AI Gateway for authentication and keeping track of currently used keys.
This commit is contained in:
@@ -14958,6 +14958,85 @@ func TestAIGatewayKeysQueries(t *testing.T) {
|
||||
requireAIGatewayKeysRow(t, keys[0], second, secondRow.CreatedAt)
|
||||
}
|
||||
|
||||
func TestGetAIGatewayKeyByHashedSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
first := aiGatewayKeyParams("lookup-first", "key_lookup1")
|
||||
second := aiGatewayKeyParams("lookup-second", "key_lookup2")
|
||||
|
||||
_, err := db.InsertAIGatewayKey(ctx, first)
|
||||
require.NoError(t, err)
|
||||
_, err = db.InsertAIGatewayKey(ctx, second)
|
||||
require.NoError(t, err)
|
||||
|
||||
key, err := db.GetAIGatewayKeyByHashedSecret(ctx, first.HashedSecret)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, first.ID, key.ID)
|
||||
require.Equal(t, first.Name, key.Name)
|
||||
require.Equal(t, first.SecretPrefix, key.SecretPrefix)
|
||||
require.Equal(t, first.HashedSecret, key.HashedSecret)
|
||||
|
||||
key, err = db.GetAIGatewayKeyByHashedSecret(ctx, second.HashedSecret)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, second.ID, key.ID)
|
||||
|
||||
// An unknown secret returns no rows
|
||||
key, err = db.GetAIGatewayKeyByHashedSecret(ctx, []byte("does-not-exist"))
|
||||
require.ErrorIs(t, err, sql.ErrNoRows)
|
||||
require.Empty(t, key.ID)
|
||||
}
|
||||
|
||||
func TestUpdateAIGatewayKeyLastUsedAt(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, _, sqlDB := dbtestutil.NewDBWithSQLDB(t)
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
params := aiGatewayKeyParams("liveness-key", "key_live___")
|
||||
row, err := db.InsertAIGatewayKey(ctx, params)
|
||||
require.NoError(t, err)
|
||||
|
||||
// last_used_at starts NULL until a session records liveness.
|
||||
keys, err := db.ListAIGatewayKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, 1)
|
||||
require.False(t, keys[0].LastUsedAt.Valid)
|
||||
|
||||
rows, err := db.UpdateAIGatewayKeyLastUsedAt(ctx, params.ID)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, rows)
|
||||
|
||||
keys, err = db.ListAIGatewayKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, 1)
|
||||
require.True(t, keys[0].LastUsedAt.Valid)
|
||||
// The database stamps the timestamp, so compare against the row's
|
||||
// DB-generated CreatedAt to avoid client clock skew.
|
||||
require.False(t, keys[0].LastUsedAt.Time.Before(row.CreatedAt))
|
||||
|
||||
// Updating a key that does not exist is a no-op, not an error.
|
||||
rows, err = db.UpdateAIGatewayKeyLastUsedAt(ctx, uuid.New())
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 0, rows)
|
||||
|
||||
// Set last_used_at to old time to confirm the update overwrites it with a fresh timestamp.
|
||||
staleTime := row.CreatedAt.Add(-time.Hour)
|
||||
_, err = sqlDB.ExecContext(ctx, "UPDATE ai_gateway_keys SET last_used_at = $1 WHERE id = $2", staleTime, params.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
rows, err = db.UpdateAIGatewayKeyLastUsedAt(ctx, params.ID)
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, 1, rows)
|
||||
|
||||
keys, err = db.ListAIGatewayKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, 1)
|
||||
require.True(t, keys[0].LastUsedAt.Time.After(staleTime))
|
||||
}
|
||||
|
||||
func aiGatewayKeyParams(name string, secretPrefix string) database.InsertAIGatewayKeyParams {
|
||||
return database.InsertAIGatewayKeyParams{
|
||||
ID: uuid.New(),
|
||||
|
||||
Reference in New Issue
Block a user