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
14 changes: 8 additions & 6 deletions app/cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ func cleanup(conn *grpc.ClientConn) error {
// 2. If the command does not require an API token, we
// 2.1 Return the token explicitly provided via the flag
// 2.2 Load the token from the environment variable and from the auth login config file
// 2.3 if they both exist, we default to the user token
// 2.3 if they both exist, we default to the API token from the environment variable
// 2.4 otherwise to the one that's set
func loadAuthToken(cmd *cobra.Command) (string, bool, error) {
// Load the APIToken from the env variable
Expand All @@ -423,11 +423,13 @@ func loadAuthToken(cmd *cobra.Command) (string, bool, error) {
return apiToken, false, nil
}

// If both the user authentication and the API token en var are set, we default to user authentication
if userToken != "" && apiTokenFromVar != "" {
logger.Warn().Msgf("Both user credentials and $%s set. Ignoring $%s.", tokenEnvVarName, tokenEnvVarName)
return userToken, true, nil
} else if apiTokenFromVar != "" {
// If the API token env var is set we default to it. An explicitly exported $CHAINLOOP_TOKEN is a
// stronger signal of intent than a (possibly stale) login session, and API tokens can now perform
// the same operations as user credentials. We warn if user credentials are also present.
if apiTokenFromVar != "" {
if userToken != "" {
logger.Warn().Msgf("Both user credentials and $%s set. Ignoring user credentials.", tokenEnvVarName)
}
return apiTokenFromVar, false, nil
}

Expand Down
113 changes: 113 additions & 0 deletions app/cli/cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright 2024-2026 The Chainloop Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"testing"

"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestLoadAuthToken(t *testing.T) {
const (
envToken = "env-token"
userTok = "user-token"
flagToken = "flag-token"
)

testCases := []struct {
name string
// inputs
envVar string
userToken string
flag string
apiTokenPref bool
expectedToken string
expectedIsUser bool
}{
{
name: "only env var set, non API-preferred command",
envVar: envToken,
expectedToken: envToken,
expectedIsUser: false,
},
{
name: "only user token set",
userToken: userTok,
expectedToken: userTok,
expectedIsUser: true,
},
{
name: "both set, command does not prefer API token, env var wins",
envVar: envToken,
userToken: userTok,
apiTokenPref: false,
expectedToken: envToken,
expectedIsUser: false,
},
{
name: "both set, command prefers API token, env var wins",
envVar: envToken,
userToken: userTok,
apiTokenPref: true,
expectedToken: envToken,
expectedIsUser: false,
},
{
name: "flag takes precedence over env var and user token",
envVar: envToken,
userToken: userTok,
flag: flagToken,
expectedToken: flagToken,
expectedIsUser: false,
},
{
name: "nothing set returns empty user token",
expectedToken: "",
expectedIsUser: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Reset and set up the global state the function relies on
viper.Reset()
t.Cleanup(viper.Reset)
viper.Set(confOptions.authToken.viperKey, tc.userToken)

if tc.envVar != "" {
t.Setenv(tokenEnvVarName, tc.envVar)
}

// apiToken is a package-level variable bound to the --token flag
prevAPIToken := apiToken
apiToken = tc.flag
t.Cleanup(func() { apiToken = prevAPIToken })

cmd := &cobra.Command{Annotations: map[string]string{}}
if tc.apiTokenPref {
cmd.Annotations[useAPIToken] = trueString
}

got, isUser, err := loadAuthToken(cmd)
require.NoError(t, err)
assert.Equal(t, tc.expectedToken, got)
assert.Equal(t, tc.expectedIsUser, isUser)
})
}
}
Loading