diff --git a/internal/cmd/project/create/create.go b/internal/cmd/project/create/create.go index ef95167e9..c33ac373c 100644 --- a/internal/cmd/project/create/create.go +++ b/internal/cmd/project/create/create.go @@ -148,29 +148,9 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, func buildRequest(ctx context.Context, model *inputModel, apiClient *resourcemanager.APIClient) (resourcemanager.ApiCreateProjectRequest, error) { req := apiClient.DefaultAPI.CreateProject(ctx) - authFlow, err := auth.GetAuthFlow() + email, err := auth.GetAuthEmail() if err != nil { - return req, fmt.Errorf("get authentication flow: %w", err) - } - var email string - switch authFlow { - case auth.AUTH_FLOW_SERVICE_ACCOUNT_TOKEN: - email, err = auth.GetAuthField(auth.SERVICE_ACCOUNT_EMAIL) - if err != nil { - return req, fmt.Errorf("get email of the service account that was used to authenticate: %w", err) - } - case auth.AUTH_FLOW_SERVICE_ACCOUNT_KEY: - email, err = auth.GetAuthField(auth.SERVICE_ACCOUNT_EMAIL) - if err != nil { - return req, fmt.Errorf("get email of the service account that was used to authenticate: %w", err) - } - case auth.AUTH_FLOW_USER_TOKEN: - email, err = auth.GetAuthField(auth.USER_EMAIL) - if err != nil { - return req, fmt.Errorf("get your user email from configuration: %w", err) - } - default: - return req, fmt.Errorf("the configured authentication flow (%s) is not supported, please report this issue", authFlow) + return req, fmt.Errorf("get email of authenticated user: %w", err) } if email == "" { diff --git a/internal/cmd/project/create/create_test.go b/internal/cmd/project/create/create_test.go index e063c6990..313dc6ec2 100644 --- a/internal/cmd/project/create/create_test.go +++ b/internal/cmd/project/create/create_test.go @@ -2,6 +2,9 @@ package create import ( "context" + "encoding/base64" + "encoding/json" + "fmt" "testing" "github.com/google/go-cmp/cmp" @@ -25,6 +28,15 @@ var testParentId = uuid.NewString() var testNetworkAreaId = uuid.NewString() var testEmail = "email" +// buildTestJWT creates an unsigned JWT token containing the given email claim. +// getEmailFromToken uses ParseUnverified, so no real signing key is needed. +func buildTestJWT(email string) string { + header, _ := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"}) + payload, _ := json.Marshal(map[string]string{"email": email}) + enc := base64.RawURLEncoding + return fmt.Sprintf("%s.%s.fakesig", enc.EncodeToString(header), enc.EncodeToString(payload)) +} + func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ parentIdFlag: testParentId, @@ -193,6 +205,7 @@ func TestBuildRequest(t *testing.T) { authFlow auth.AuthFlow sa_email *string user_email *string + accessToken *string expectedRequest resourcemanager.ApiCreateProjectRequest isValid bool }{ @@ -220,6 +233,13 @@ func TestBuildRequest(t *testing.T) { expectedRequest: fixtureRequest(), isValid: true, }, + { + description: "access_token_env_var_no_stored_auth_flow", + model: fixtureInputModel(), + accessToken: utils.Ptr(buildTestJWT(testEmail)), + expectedRequest: fixtureRequest(), + isValid: true, + }, { description: "missing_network_area_id sa_key", model: fixtureInputModel( @@ -296,6 +316,9 @@ func TestBuildRequest(t *testing.T) { t.Fatalf("Failed to set user email in storage: %v", err) } } + if tt.accessToken != nil { + t.Setenv("STACKIT_ACCESS_TOKEN", *tt.accessToken) + } request, err := buildRequest(testCtx, tt.model, testClient) if err != nil { if !tt.isValid { diff --git a/internal/pkg/auth/auth.go b/internal/pkg/auth/auth.go index 3d241ed9a..d50f7b966 100644 --- a/internal/pkg/auth/auth.go +++ b/internal/pkg/auth/auth.go @@ -134,6 +134,9 @@ func UserSessionExpired() (bool, error) { } func GetAccessToken() (string, error) { + if accessToken := os.Getenv(envAccessTokenName); accessToken != "" { + return accessToken, nil + } accessToken, err := GetAuthField(ACCESS_TOKEN) if err != nil { return "", fmt.Errorf("get %s: %w", ACCESS_TOKEN, err) diff --git a/internal/pkg/auth/auth_test.go b/internal/pkg/auth/auth_test.go index 192645d21..b3418e731 100644 --- a/internal/pkg/auth/auth_test.go +++ b/internal/pkg/auth/auth_test.go @@ -335,3 +335,68 @@ func TestInitKeyFlow(t *testing.T) { }) } } + +func TestGetAccessToken_EnvVar(t *testing.T) { + const envValue = "token-from-env" + const storedValue = "stored-token" + + tests := []struct { + description string + envToken string + storedToken string + expectedToken string + isValid bool + }{ + { + description: "env var set and no stored token", + envToken: envValue, + expectedToken: envValue, + isValid: true, + }, + { + description: "env var set and stored token present", + envToken: envValue, + storedToken: storedValue, + expectedToken: envValue, + isValid: true, + }, + { + description: "env var not set and stored token present", + storedToken: storedValue, + expectedToken: storedValue, + isValid: true, + }, + { + description: "env var not set and no stored token", + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + keyring.MockInit() + if tt.envToken != "" { + t.Setenv(envAccessTokenName, tt.envToken) + } + if tt.storedToken != "" { + if err := SetAuthField(ACCESS_TOKEN, tt.storedToken); err != nil { + t.Fatalf("Failed to set stored token: %v", err) + } + if err := SetAuthFlow(AUTH_FLOW_SERVICE_ACCOUNT_TOKEN); err != nil { + t.Fatalf("Failed to set auth flow: %v", err) + } + } + + got, err := GetAccessToken() + if err != nil { + if !tt.isValid { + return + } + t.Fatalf("unexpected error: %v", err) + } + if tt.expectedToken != got { + t.Errorf("expected token %q, got %q", tt.expectedToken, got) + } + }) + } +}