Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions cmd/cloudx/client/api_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,33 @@ import (
"context"
"errors"
"fmt"
"time"

cloud "github.com/ory/client-go"
"github.com/ory/x/cmdx"
)

func (h *CommandHelper) CreateProjectAPIKey(ctx context.Context, projectID, name string) (*cloud.ProjectApiKey, error) {
// CreateProjectAPIKey creates a project API key. If expiresIn is greater than
// zero, the key is set to expire that duration from now so it is cleaned up
// automatically on the server side even if local cleanup fails. An expiresIn of
// zero creates a key without expiry; a negative value is rejected.
func (h *CommandHelper) CreateProjectAPIKey(ctx context.Context, projectID, name string, expiresIn time.Duration) (*cloud.ProjectApiKey, error) {
if expiresIn < 0 {
return nil, errors.New("API key expiry must not be negative")
}

c, err := h.newConsoleAPIClient(ctx)
if err != nil {
return nil, err
}

token, res, err := c.ProjectAPI.CreateProjectApiKey(ctx, projectID).CreateProjectApiKeyRequest(cloud.CreateProjectApiKeyRequest{Name: name}).Execute()
req := cloud.CreateProjectApiKeyRequest{Name: name}
if expiresIn > 0 {
expiresAt := time.Now().Add(expiresIn)
req.ExpiresAt = &expiresAt
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

token, res, err := c.ProjectAPI.CreateProjectApiKey(ctx, projectID).CreateProjectApiKeyRequest(req).Execute()
if err != nil {
return nil, handleError("unable to create project API key", res, err)
}
Expand Down Expand Up @@ -64,7 +79,11 @@ func (h *CommandHelper) DeleteWorkspaceAPIKey(ctx context.Context, workspaceID,
return nil
}

func (h *CommandHelper) TemporaryAPIKey(ctx context.Context, name string) (apiKey string, cleanup func() error, err error) {
// TemporaryAPIKey creates a short-lived project API key that is deleted via the
// returned cleanup function. The key is additionally set to expire after
// expiresIn so that it is removed automatically should the cleanup fail. An
// expiresIn of zero creates a key without expiry.
func (h *CommandHelper) TemporaryAPIKey(ctx context.Context, name string, expiresIn time.Duration) (apiKey string, cleanup func() error, err error) {
if h.projectAPIKey != nil {
return *h.projectAPIKey, noop, nil
}
Expand Down Expand Up @@ -95,7 +114,7 @@ func (h *CommandHelper) TemporaryAPIKey(ctx context.Context, name string) (apiKe
if err != nil {
return "", noop, err
}
ak, err := h.CreateProjectAPIKey(ctx, projectID, name)
ak, err := h.CreateProjectAPIKey(ctx, projectID, name, expiresIn)
if err != nil {
_, _ = fmt.Fprintf(h.VerboseErrWriter, "Unable to create API key. Do you have the required permissions to use the Ory CLI with project %q? Continuing without API key.", projectID)
return "", noop, nil
Expand Down
11 changes: 7 additions & 4 deletions cmd/cloudx/client/command_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func TestCommandHelper(t *testing.T) {
require.Len(t, projects, 2)
assert.ElementsMatch(t, []string{p0.Id, p1.Id}, []string{projects[0].Id, projects[1].Id})

pjKey, err := authenticated.CreateProjectAPIKey(ctx, p0.Id, "test key")
pjKey, err := authenticated.CreateProjectAPIKey(ctx, p0.Id, "test key", 0)
require.NoError(t, err)

pjKeyH, err := client.NewCommandHelper(ctx, client.WithProjectAPIKey(*pjKey.Value))
Expand Down Expand Up @@ -192,7 +192,7 @@ func TestCommandHelper(t *testing.T) {
require.Len(t, projects, 2)
assert.ElementsMatch(t, []string{p0.Id, p1.Id}, []string{projects[0].Id, projects[1].Id})

pjKey, err := authenticated.CreateProjectAPIKey(ctx, p0.Id, "test key")
pjKey, err := authenticated.CreateProjectAPIKey(ctx, p0.Id, "test key", 0)
require.NoError(t, err)

pjKeyH, err := client.NewCommandHelper(ctx, client.WithProjectAPIKey(*pjKey.Value))
Expand Down Expand Up @@ -310,10 +310,13 @@ func TestCommandHelper(t *testing.T) {

keyName := "a test key"

key, err := authenticated.CreateProjectAPIKey(ctx, defaultProject.Id, keyName)
key, err := authenticated.CreateProjectAPIKey(ctx, defaultProject.Id, keyName, time.Hour)
require.NoError(t, err)
assert.Equal(t, keyName, key.Name)
assert.NotNil(t, keyName, key.Value)
// The requested expiry should be reflected on the returned key.
require.NotNil(t, key.ExpiresAt)
assert.WithinDuration(t, time.Now().Add(time.Hour), *key.ExpiresAt, 5*time.Minute)

// check that the key works
ctxWithKey := client.ContextWithOptions(ctx,
Expand All @@ -333,7 +336,7 @@ func TestCommandHelper(t *testing.T) {
t.Run("func=GetProject", func(t *testing.T) {
wsKeyH, err := client.NewCommandHelper(ctx, client.WithWorkspaceAPIKey(*defaultWorkspaceAPIKey.Value))
require.NoError(t, err)
pjKey, err := authenticated.CreateProjectAPIKey(ctx, defaultProject.Id, "test key")
pjKey, err := authenticated.CreateProjectAPIKey(ctx, defaultProject.Id, "test key", 0)
require.NoError(t, err)
pjKeyH, err := client.NewCommandHelper(ctx, client.WithProjectAPIKey(*pjKey.Value))
require.NoError(t, err)
Expand Down
13 changes: 12 additions & 1 deletion cmd/cloudx/proxy/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ const (
CORSFlag = "allowed-cors-origins"
AdditionalCORSHeadersFlag = "additional-cors-headers"
RewriteHostFlag = "rewrite-host"
APIKeyExpiryFlag = "api-key-expiry"
)

// defaultAPIKeyExpiry is the default lifetime of the temporary API key the
// proxy and tunnel create to configure the project.
const defaultAPIKeyExpiry = 12 * time.Hour

type config struct {
port int
open bool
Expand All @@ -70,6 +75,11 @@ type config struct {
corsOrigins []string
additionalCorsHeaders []string

// apiKeyExpiry is the lifetime of the temporary API key the proxy/tunnel
// creates. The key is deleted on shutdown; the expiry ensures it is removed
// automatically should that cleanup fail. A value of zero disables expiry.
apiKeyExpiry time.Duration

// rewriteHost means the host header will be rewritten to the upstream host.
// This is useful in cases where upstream resolves requests based on Host.
rewriteHost bool
Expand All @@ -89,6 +99,7 @@ func registerConfigFlags(conf *config, flags *pflag.FlagSet) {
flags.BoolVar(&conf.isDev, DevFlag, true, "This flag is deprecated as the command is only supposed to be used during development.")
flags.BoolVar(&conf.isDebug, DebugFlag, false, "Use this flag to debug, for example, CORS requests.")
flags.BoolVar(&conf.rewriteHost, RewriteHostFlag, false, "Use this flag to rewrite the host header to the upstream host.")
flags.DurationVar(&conf.apiKeyExpiry, APIKeyExpiryFlag, defaultAPIKeyExpiry, "Sets the expiry of the temporary API key the Ory CLI creates to configure your project. The key is deleted on shutdown; this expiry ensures it is removed automatically if that cleanup fails. Set to 0 to disable expiry.")
}

func portFromEnv() int {
Expand All @@ -115,7 +126,7 @@ func runReverseProxy(ctx context.Context, h *client.CommandHelper, stdErr io.Wri
return err
}

apiKey, removeAPIKey, err := h.TemporaryAPIKey(ctx, fmt.Sprintf("Ory %s temporary API key - %s", name, h.UserName(ctx)))
apiKey, removeAPIKey, err := h.TemporaryAPIKey(ctx, fmt.Sprintf("Ory %s temporary API key - %s", name, h.UserName(ctx)), conf.apiKeyExpiry)
if err != nil {
return err
}
Expand Down
Loading