chore: pass lifetime directly into api key generate (#11715)

Rather than passing all the deployment values.  This is to make it
easier to generate API keys as part of the oauth flow.

I also added and fixed a test for when the lifetime is set and the
default and expiration are unset.

Co-authored-by: Steven Masley <stevenmasley@gmail.com>
This commit is contained in:
Asher 2024-01-22 11:42:55 -09:00 committed by GitHub
parent a31d19d538
commit 16c6cefde8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 88 additions and 78 deletions

View File

@ -82,13 +82,13 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
}
cookie, key, err := api.createAPIKey(ctx, apikey.CreateParams{
UserID: user.ID,
LoginType: database.LoginTypeToken,
DeploymentValues: api.DeploymentValues,
ExpiresAt: dbtime.Now().Add(lifeTime),
Scope: scope,
LifetimeSeconds: int64(lifeTime.Seconds()),
TokenName: tokenName,
UserID: user.ID,
LoginType: database.LoginTypeToken,
DefaultLifetime: api.DeploymentValues.SessionDuration.Value(),
ExpiresAt: dbtime.Now().Add(lifeTime),
Scope: scope,
LifetimeSeconds: int64(lifeTime.Seconds()),
TokenName: tokenName,
})
if err != nil {
if database.IsUniqueViolation(err, database.UniqueIndexAPIKeyName) {
@ -127,10 +127,10 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
lifeTime := time.Hour * 24 * 7
cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{
UserID: user.ID,
DeploymentValues: api.DeploymentValues,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
UserID: user.ID,
DefaultLifetime: api.DeploymentValues.SessionDuration.Value(),
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
// All api generated keys will last 1 week. Browser login tokens have
// a shorter life.
ExpiresAt: dbtime.Now().Add(lifeTime),

View File

@ -12,14 +12,15 @@ import (
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/cryptorand"
)
type CreateParams struct {
UserID uuid.UUID
LoginType database.LoginType
DeploymentValues *codersdk.DeploymentValues
UserID uuid.UUID
LoginType database.LoginType
// DefaultLifetime is configured in DeploymentValues.
// It is used if both ExpiresAt and LifetimeSeconds are not set.
DefaultLifetime time.Duration
// Optional.
ExpiresAt time.Time
@ -46,8 +47,8 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
if params.LifetimeSeconds != 0 {
params.ExpiresAt = dbtime.Now().Add(time.Duration(params.LifetimeSeconds) * time.Second)
} else {
params.ExpiresAt = dbtime.Now().Add(params.DeploymentValues.SessionDuration.Value())
params.LifetimeSeconds = int64(params.DeploymentValues.SessionDuration.Value().Seconds())
params.ExpiresAt = dbtime.Now().Add(params.DefaultLifetime)
params.LifetimeSeconds = int64(params.DefaultLifetime.Seconds())
}
}
if params.LifetimeSeconds == 0 {

View File

@ -10,11 +10,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/coderd/apikey"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/codersdk"
)
func TestGenerate(t *testing.T) {
@ -30,38 +28,36 @@ func TestGenerate(t *testing.T) {
{
name: "OK",
params: apikey.CreateParams{
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DeploymentValues: &codersdk.DeploymentValues{},
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScopeApplicationConnect,
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DefaultLifetime: time.Duration(0),
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScopeApplicationConnect,
},
},
{
name: "InvalidScope",
params: apikey.CreateParams{
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DeploymentValues: &codersdk.DeploymentValues{},
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScope("test"),
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DefaultLifetime: time.Duration(0),
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScope("test"),
},
fail: true,
},
{
name: "DeploymentSessionDuration",
params: apikey.CreateParams{
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DeploymentValues: &codersdk.DeploymentValues{
SessionDuration: clibase.Duration(time.Hour),
},
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DefaultLifetime: time.Hour,
LifetimeSeconds: 0,
ExpiresAt: time.Time{},
TokenName: "hello",
@ -69,30 +65,43 @@ func TestGenerate(t *testing.T) {
Scope: database.APIKeyScopeApplicationConnect,
},
},
{
name: "LifetimeSeconds",
params: apikey.CreateParams{
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DefaultLifetime: time.Duration(0),
LifetimeSeconds: int64(time.Hour.Seconds()),
ExpiresAt: time.Time{},
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: database.APIKeyScopeApplicationConnect,
},
},
{
name: "DefaultIP",
params: apikey.CreateParams{
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DeploymentValues: &codersdk.DeploymentValues{},
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "",
Scope: database.APIKeyScopeApplicationConnect,
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DefaultLifetime: time.Duration(0),
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "",
Scope: database.APIKeyScopeApplicationConnect,
},
},
{
name: "DefaultScope",
params: apikey.CreateParams{
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DeploymentValues: &codersdk.DeploymentValues{},
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: "",
UserID: uuid.New(),
LoginType: database.LoginTypeOIDC,
DefaultLifetime: time.Duration(0),
ExpiresAt: time.Now().Add(time.Hour),
LifetimeSeconds: int64(time.Hour.Seconds()),
TokenName: "hello",
RemoteAddr: "1.2.3.4",
Scope: "",
},
},
}
@ -131,15 +140,15 @@ func TestGenerate(t *testing.T) {
// Should not be a delta greater than 5 seconds.
assert.InDelta(t, time.Until(tc.params.ExpiresAt).Seconds(), key.LifetimeSeconds, 5)
} else {
assert.Equal(t, int64(tc.params.DeploymentValues.SessionDuration.Value().Seconds()), key.LifetimeSeconds)
assert.Equal(t, int64(tc.params.DefaultLifetime.Seconds()), key.LifetimeSeconds)
}
if !tc.params.ExpiresAt.IsZero() {
assert.Equal(t, tc.params.ExpiresAt.UTC(), key.ExpiresAt)
} else if tc.params.LifetimeSeconds > 0 {
assert.WithinDuration(t, dbtime.Now().Add(time.Duration(tc.params.LifetimeSeconds)), key.ExpiresAt, time.Second*5)
assert.WithinDuration(t, dbtime.Now().Add(time.Duration(tc.params.LifetimeSeconds)*time.Second), key.ExpiresAt, time.Second*5)
} else {
assert.WithinDuration(t, dbtime.Now().Add(tc.params.DeploymentValues.SessionDuration.Value()), key.ExpiresAt, time.Second*5)
assert.WithinDuration(t, dbtime.Now().Add(tc.params.DefaultLifetime), key.ExpiresAt, time.Second*5)
}
if tc.params.RemoteAddr != "" {

View File

@ -1683,11 +1683,11 @@ func workspaceSessionTokenName(workspace database.Workspace) string {
func (s *server) regenerateSessionToken(ctx context.Context, user database.User, workspace database.Workspace) (string, error) {
newkey, sessionToken, err := apikey.Generate(apikey.CreateParams{
UserID: user.ID,
LoginType: user.LoginType,
DeploymentValues: s.DeploymentValues,
TokenName: workspaceSessionTokenName(workspace),
LifetimeSeconds: int64(s.DeploymentValues.MaxTokenLifetime.Value().Seconds()),
UserID: user.ID,
LoginType: user.LoginType,
DefaultLifetime: s.DeploymentValues.SessionDuration.Value(),
TokenName: workspaceSessionTokenName(workspace),
LifetimeSeconds: int64(s.DeploymentValues.MaxTokenLifetime.Value().Seconds()),
})
if err != nil {
return "", xerrors.Errorf("generate API key: %w", err)

View File

@ -247,10 +247,10 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
//nolint:gocritic // Creating the API key as the user instead of as system.
cookie, key, err := api.createAPIKey(dbauthz.As(ctx, userSubj), apikey.CreateParams{
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
DeploymentValues: api.DeploymentValues,
UserID: user.ID,
LoginType: database.LoginTypePassword,
RemoteAddr: r.RemoteAddr,
DefaultLifetime: api.DeploymentValues.SessionDuration.Value(),
})
if err != nil {
logger.Error(ctx, "unable to create API key", slog.Error(err))
@ -1545,10 +1545,10 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
} else {
//nolint:gocritic
cookie, newKey, err := api.createAPIKey(dbauthz.AsSystemRestricted(ctx), apikey.CreateParams{
UserID: user.ID,
LoginType: params.LoginType,
DeploymentValues: api.DeploymentValues,
RemoteAddr: r.RemoteAddr,
UserID: user.ID,
LoginType: params.LoginType,
DefaultLifetime: api.DeploymentValues.SessionDuration.Value(),
RemoteAddr: r.RemoteAddr,
})
if err != nil {
return nil, database.APIKey{}, xerrors.Errorf("create API key: %w", err)

View File

@ -107,12 +107,12 @@ func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request
lifetimeSeconds = int64(api.DeploymentValues.SessionDuration.Value().Seconds())
}
cookie, _, err := api.createAPIKey(ctx, apikey.CreateParams{
UserID: apiKey.UserID,
LoginType: database.LoginTypePassword,
DeploymentValues: api.DeploymentValues,
ExpiresAt: exp,
LifetimeSeconds: lifetimeSeconds,
Scope: database.APIKeyScopeApplicationConnect,
UserID: apiKey.UserID,
LoginType: database.LoginTypePassword,
DefaultLifetime: api.DeploymentValues.SessionDuration.Value(),
ExpiresAt: exp,
LifetimeSeconds: lifetimeSeconds,
Scope: database.APIKeyScopeApplicationConnect,
})
if err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{