mirror of
https://github.com/coder/coder.git
synced 2025-07-01 16:07:26 +00:00
* chore: merge apikey/token session config values There is a confusing difference between an apikey and a token. This difference leaks into our configs. This change does not resolve the difference. It only groups the config values to try and manage any bloat that occurs from adding more similar config values
227 lines
7.5 KiB
Go
227 lines
7.5 KiB
Go
package coderd_test
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/audit"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
func TestTokenCRUD(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
auditor := audit.NewMock()
|
|
numLogs := len(auditor.AuditLogs())
|
|
client := coderdtest.New(t, &coderdtest.Options{Auditor: auditor})
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
numLogs++ // add an audit log for user creation
|
|
|
|
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
|
require.NoError(t, err)
|
|
require.Empty(t, keys)
|
|
|
|
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
|
|
require.NoError(t, err)
|
|
require.Greater(t, len(res.Key), 2)
|
|
numLogs++ // add an audit log for token creation
|
|
|
|
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, len(keys), 1)
|
|
require.Contains(t, res.Key, keys[0].ID)
|
|
// expires_at should default to 30 days
|
|
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
|
|
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
|
|
require.Equal(t, codersdk.APIKeyScopeAll, keys[0].Scope)
|
|
|
|
// no update
|
|
|
|
err = client.DeleteAPIKey(ctx, codersdk.Me, keys[0].ID)
|
|
require.NoError(t, err)
|
|
numLogs++ // add an audit log for token deletion
|
|
keys, err = client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
|
require.NoError(t, err)
|
|
require.Empty(t, keys)
|
|
|
|
// ensure audit log count is correct
|
|
require.Len(t, auditor.AuditLogs(), numLogs)
|
|
require.Equal(t, database.AuditActionCreate, auditor.AuditLogs()[numLogs-2].Action)
|
|
require.Equal(t, database.AuditActionDelete, auditor.AuditLogs()[numLogs-1].Action)
|
|
}
|
|
|
|
func TestTokenScoped(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
client := coderdtest.New(t, nil)
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
|
|
res, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
|
Scope: codersdk.APIKeyScopeApplicationConnect,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Greater(t, len(res.Key), 2)
|
|
|
|
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, len(keys), 1)
|
|
require.Contains(t, res.Key, keys[0].ID)
|
|
require.Equal(t, keys[0].Scope, codersdk.APIKeyScopeApplicationConnect)
|
|
}
|
|
|
|
func TestUserSetTokenDuration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
client := coderdtest.New(t, nil)
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
|
|
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
|
Lifetime: time.Hour * 24 * 7,
|
|
})
|
|
require.NoError(t, err)
|
|
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
|
require.NoError(t, err)
|
|
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*6*24))
|
|
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*8*24))
|
|
}
|
|
|
|
func TestDefaultTokenDuration(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
client := coderdtest.New(t, nil)
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
|
|
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{})
|
|
require.NoError(t, err)
|
|
keys, err := client.Tokens(ctx, codersdk.Me, codersdk.TokensFilter{})
|
|
require.NoError(t, err)
|
|
require.Greater(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*29*24))
|
|
require.Less(t, keys[0].ExpiresAt, time.Now().Add(time.Hour*31*24))
|
|
}
|
|
|
|
func TestTokenUserSetMaxLifetime(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
dc := coderdtest.DeploymentValues(t)
|
|
dc.Sessions.MaximumTokenDuration = serpent.Duration(time.Hour * 24 * 7)
|
|
client := coderdtest.New(t, &coderdtest.Options{
|
|
DeploymentValues: dc,
|
|
})
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
|
|
// success
|
|
_, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
|
Lifetime: time.Hour * 24 * 6,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// fail
|
|
_, err = client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{
|
|
Lifetime: time.Hour * 24 * 8,
|
|
})
|
|
require.ErrorContains(t, err, "lifetime must be less")
|
|
}
|
|
|
|
func TestSessionExpiry(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
dc := coderdtest.DeploymentValues(t)
|
|
|
|
db, pubsub := dbtestutil.NewDB(t)
|
|
adminClient := coderdtest.New(t, &coderdtest.Options{
|
|
DeploymentValues: dc,
|
|
Database: db,
|
|
Pubsub: pubsub,
|
|
})
|
|
adminUser := coderdtest.CreateFirstUser(t, adminClient)
|
|
|
|
// This is a hack, but we need the admin account to have a long expiry
|
|
// otherwise the test will flake, so we only update the expiry config after
|
|
// the admin account has been created.
|
|
//
|
|
// We don't support updating the deployment config after startup, but for
|
|
// this test it works because we don't copy the value (and we use pointers).
|
|
dc.Sessions.DefaultDuration = serpent.Duration(time.Second)
|
|
|
|
userClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID)
|
|
|
|
// Find the session cookie, and ensure it has the correct expiry.
|
|
token := userClient.SessionToken()
|
|
apiKey, err := db.GetAPIKeyByID(ctx, strings.Split(token, "-")[0])
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, dc.Sessions.DefaultDuration.Value().Seconds(), apiKey.LifetimeSeconds)
|
|
require.WithinDuration(t, apiKey.CreatedAt.Add(dc.Sessions.DefaultDuration.Value()), apiKey.ExpiresAt, 2*time.Second)
|
|
|
|
// Update the session token to be expired so we can test that it is
|
|
// rejected for extra points.
|
|
err = db.UpdateAPIKeyByID(ctx, database.UpdateAPIKeyByIDParams{
|
|
ID: apiKey.ID,
|
|
LastUsed: apiKey.LastUsed,
|
|
ExpiresAt: dbtime.Now().Add(-time.Hour),
|
|
IPAddress: apiKey.IPAddress,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
_, err = userClient.User(ctx, codersdk.Me)
|
|
require.Error(t, err)
|
|
var sdkErr *codersdk.Error
|
|
if assert.ErrorAs(t, err, &sdkErr) {
|
|
require.Equal(t, http.StatusUnauthorized, sdkErr.StatusCode())
|
|
require.Contains(t, sdkErr.Message, "session has expired")
|
|
}
|
|
}
|
|
|
|
func TestAPIKey_OK(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
_ = coderdtest.CreateFirstUser(t, client)
|
|
|
|
res, err := client.CreateAPIKey(ctx, codersdk.Me)
|
|
require.NoError(t, err)
|
|
require.Greater(t, len(res.Key), 2)
|
|
}
|
|
|
|
func TestAPIKey_Deleted(t *testing.T) {
|
|
t.Parallel()
|
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
|
defer cancel()
|
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
|
user := coderdtest.CreateFirstUser(t, client)
|
|
_, anotherUser := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
|
|
require.NoError(t, client.DeleteUser(context.Background(), anotherUser.ID))
|
|
|
|
// Attempt to create an API key for the deleted user. This should fail.
|
|
_, err := client.CreateAPIKey(ctx, anotherUser.Username)
|
|
require.Error(t, err)
|
|
var apiErr *codersdk.Error
|
|
require.ErrorAs(t, err, &apiErr)
|
|
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
|
}
|