Skip to content

Commit 78d3cfe

Browse files
committed
Refined the token mask logic
1 parent 335f923 commit 78d3cfe

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

internal/cmd/cloud_login.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,7 @@ func printConfig(gs *state.GlobalState, cloudConf cloudapi.Config) {
210210
token, stackID, stackURL, defProj := notSet, notSet, notSet, notSet
211211

212212
if cloudConf.Token.String != "" {
213-
// If a token is set then we assume we have a valid token longer than 8 chars
214-
// print the token with all the chars masked, except the first and the last four
215-
unmasked := cloudConf.Token.String
216-
asterisks := strings.Repeat("*", len(unmasked)-8)
217-
token = unmasked[:4] + asterisks + unmasked[len(unmasked)-4:]
213+
token = maskToken(cloudConf.Token.String)
218214
}
219215
if cloudConf.StackID.Valid {
220216
stackID = strconv.FormatInt(cloudConf.StackID.Int64, 10)
@@ -233,6 +229,22 @@ func printConfig(gs *state.GlobalState, cloudConf cloudapi.Config) {
233229
printToStdout(gs, fmt.Sprintf(" default-project-id: %s\n", valueColor.Sprint(defProj)))
234230
}
235231

232+
func maskToken(unmasked string) string {
233+
if len(unmasked) < 1 {
234+
return ""
235+
}
236+
// Require at least 4 asterisks in the middle to give a meaningful visual hint.
237+
// Any token shorter than 12 chars would produce fewer, so mask it entirely.
238+
if len(unmasked) < 12 {
239+
return strings.Repeat("*", len(unmasked))
240+
}
241+
// We try to have a good DX here.
242+
// A valid Cloud token should be 12+ chars, so it prints the token with all
243+
// the chars masked, except the first and the last four.
244+
asterisks := strings.Repeat("*", len(unmasked)-8)
245+
return unmasked[:4] + asterisks + unmasked[len(unmasked)-4:]
246+
}
247+
236248
// tokenAuthentication validates a token and a stack
237249
// and update the config with the given inputs
238250
func authenticateUserToken(

internal/cmd/cloud_login_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package cmd
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"gopkg.in/guregu/null.v3"
9+
10+
"go.k6.io/k6/cloudapi"
11+
"go.k6.io/k6/internal/cmd/tests"
12+
)
13+
14+
func TestMaskToken(t *testing.T) {
15+
t.Parallel()
16+
17+
testCases := []struct {
18+
name string
19+
token string
20+
expected string
21+
}{
22+
{
23+
name: "empty string returns empty string",
24+
token: "",
25+
expected: "",
26+
},
27+
{
28+
name: "single character is fully masked",
29+
token: "a",
30+
expected: "*",
31+
},
32+
{
33+
name: "four characters are fully masked",
34+
token: "abcd",
35+
expected: "****",
36+
},
37+
{
38+
name: "eleven characters are fully masked",
39+
token: "abcdefghijk",
40+
expected: "***********",
41+
},
42+
{
43+
name: "twelve characters masks the middle four",
44+
token: "abcdefghijkl",
45+
expected: "abcd****ijkl",
46+
},
47+
{
48+
name: "long token masks all but first and last four",
49+
token: "tok_abcdefghijklmnopqrstuvwxyz1234",
50+
expected: "tok_" + strings.Repeat("*", 26) + "1234",
51+
},
52+
}
53+
54+
for _, tc := range testCases {
55+
t.Run(tc.name, func(t *testing.T) {
56+
t.Parallel()
57+
58+
got := maskToken(tc.token)
59+
assert.Equal(t, tc.expected, got)
60+
})
61+
}
62+
}
63+
64+
func TestPrintConfigTokenOutput(t *testing.T) {
65+
t.Parallel()
66+
67+
testCases := []struct {
68+
name string
69+
token string
70+
expected string
71+
}{
72+
{
73+
name: "unset token shows not set placeholder",
74+
token: "",
75+
expected: "<not set>",
76+
},
77+
{
78+
name: "token masked",
79+
token: "abcdefghijkl",
80+
expected: "abcd****ijkl",
81+
},
82+
}
83+
84+
for _, tc := range testCases {
85+
t.Run(tc.name, func(t *testing.T) {
86+
t.Parallel()
87+
88+
ts := tests.NewGlobalTestState(t)
89+
conf := cloudapi.Config{}
90+
if tc.token != "" {
91+
conf.Token = null.StringFrom(tc.token)
92+
}
93+
94+
printConfig(ts.GlobalState, conf)
95+
assert.Contains(t, ts.Stdout.String(), " token: "+tc.expected)
96+
})
97+
}
98+
}

internal/cmd/tests/cmd_cloud_login_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"net/http"
66
"net/http/httptest"
7+
"strings"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -37,7 +38,7 @@ func TestCloudLoginWithArgs(t *testing.T) {
3738
wantErr: false,
3839
wantStdoutContains: []string{
3940
"Logged in successfully",
40-
fmt.Sprintf("token: %s", "vali***oken"),
41+
fmt.Sprintf("token: %s", strings.Repeat("*", 11)),
4142
fmt.Sprintf("stack-id: %d", validStackID),
4243
fmt.Sprintf("stack-url: %s", validStackURL),
4344
fmt.Sprintf("default-project-id: %d", defaultProjectID),

0 commit comments

Comments
 (0)