Skip to content

Commit ae6656b

Browse files
committed
v0.0.2: fixed keychain bug, added token validation
1 parent d2348ef commit ae6656b

File tree

4 files changed

+65
-25
lines changed

4 files changed

+65
-25
lines changed

api/api.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"mime"
99
"net/http"
1010
"net/url"
11+
"strings"
1112

1213
"github.com/anchordotdev/cli"
1314
"github.com/anchordotdev/cli/keyring"
@@ -40,7 +41,10 @@ func Client(cfg *cli.Config) (*http.Client, error) {
4041
if apiToken, err = kr.Get(keyring.APIToken); err == keyring.ErrNotFound {
4142
return client, ErrSignedOut
4243
} else if err != nil {
43-
return nil, fmt.Errorf("reading API token from keyring failed: %w", err)
44+
return nil, fmt.Errorf("reading PAT token from keyring failed: %w", err)
45+
}
46+
if !strings.HasPrefix(apiToken, "ap0_") || len(apiToken) != 64 {
47+
return nil, fmt.Errorf("read invalid PAT token from keyring")
4448
}
4549
}
4650

auth/signin.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package auth
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"net/http"
89
"strings"
@@ -15,6 +16,10 @@ import (
1516
"github.com/anchordotdev/cli/keyring"
1617
)
1718

19+
var (
20+
ErrSigninFailed = errors.New("sign in failed")
21+
)
22+
1823
type SignIn struct {
1924
Config *cli.Config
2025
}
@@ -26,17 +31,23 @@ func (s SignIn) TUI() cli.TUI {
2631
}
2732

2833
func (s *SignIn) run(ctx context.Context, tty termenv.File) error {
29-
apiToken := s.Config.API.Token
30-
if len(apiToken) == 0 {
31-
if _, err := fmt.Fprintf(tty, "API Token: "); err != nil {
34+
if len(s.Config.API.Token) == 0 {
35+
fmt.Fprintf(tty, "To complete sign in, please:\n 1. Visit https://anchor.dev/settings and add a new Personal Access Token (PAT).\n 2. Copy the key from the new token and paste it below when prompted.\n")
36+
37+
if _, err := fmt.Fprintf(tty, "Personal Access Token (PAT): "); err != nil {
3238
return err
3339
}
3440

3541
line, err := term.ReadPassword(int(tty.Fd()))
3642
if err != nil {
3743
return err
3844
}
39-
apiToken = strings.TrimSpace(string(line))
45+
pat := strings.TrimSpace(string(line))
46+
if !strings.HasPrefix(pat, "ap0_") || len(pat) != 64 {
47+
return fmt.Errorf("invalid PAT key")
48+
}
49+
50+
s.Config.API.Token = pat
4051

4152
if _, err := fmt.Fprintln(tty); err != nil {
4253
return err
@@ -52,12 +63,15 @@ func (s *SignIn) run(ctx context.Context, tty termenv.File) error {
5263
if err != nil {
5364
return err
5465
}
55-
req.SetBasicAuth(apiToken, "")
66+
req.SetBasicAuth(s.Config.API.Token, "")
5667

5768
res, err := anc.Do(req)
5869
if err != nil {
5970
return err
6071
}
72+
if res.StatusCode == http.StatusForbidden {
73+
return ErrSigninFailed
74+
}
6175
if res.StatusCode != http.StatusOK {
6276
return fmt.Errorf("unexpected response code: %d", res.StatusCode)
6377
}
@@ -68,7 +82,7 @@ func (s *SignIn) run(ctx context.Context, tty termenv.File) error {
6882
}
6983

7084
kr := keyring.Keyring{Config: s.Config}
71-
if err := kr.Set(keyring.APIToken, apiToken); err != nil {
85+
if err := kr.Set(keyring.APIToken, s.Config.API.Token); err != nil {
7286
return err
7387
}
7488

auth/signin_test.go

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,41 @@ func TestSignIn(t *testing.T) {
1616
cfg.API.URL = srv.URL
1717
cfg.Keyring.MockMode = true
1818

19-
var err error
20-
if cfg.API.Token, err = srv.GeneratePAT("example@example.com"); err != nil {
21-
t.Fatal(err)
22-
}
23-
24-
cmd := &SignIn{
25-
Config: cfg,
26-
}
27-
28-
buf, err := apitest.RunTUI(ctx, cmd.TUI())
29-
if err != nil {
30-
t.Fatal(err)
31-
}
32-
33-
if want, got := "Success, hello example@example.com!\n", buf.String(); want != got {
34-
t.Errorf("want output %q, got %q", want, got)
35-
}
19+
t.Run("valid-token", func(t *testing.T) {
20+
var err error
21+
if cfg.API.Token, err = srv.GeneratePAT("example@example.com"); err != nil {
22+
t.Fatal(err)
23+
}
24+
25+
cmd := &SignIn{
26+
Config: cfg,
27+
}
28+
29+
buf, err := apitest.RunTUI(ctx, cmd.TUI())
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
if want, got := "Success, hello example@example.com!\n", buf.String(); want != got {
35+
t.Errorf("want output %q, got %q", want, got)
36+
}
37+
})
38+
39+
t.Run("invalid-token", func(t *testing.T) {
40+
if !srv.IsProxy() {
41+
t.Skip("server doesn't authenticate in mock mode")
42+
return
43+
}
44+
45+
cfg.API.Token = "bad-pat-token"
46+
47+
cmd := &SignIn{
48+
Config: cfg,
49+
}
50+
51+
_, err := apitest.RunTUI(ctx, cmd.TUI())
52+
if want, got := ErrSigninFailed, err; want != got {
53+
t.Fatalf("want signin failure error %q, got %q", want, got)
54+
}
55+
})
3656
}

command.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ type Command struct {
3030
func (c *Command) Execute(ctx context.Context, cfg *Config) error {
3131
defaults.SetDefaults(cfg)
3232

33+
cmd := c.cobraCommand(ctx, reflect.ValueOf(cfg))
34+
3335
if err := envdecode.Decode(cfg); err != nil && err != envdecode.ErrNoTargetFieldsAreSet {
3436
return err
3537
}
3638

37-
return c.cobraCommand(ctx, reflect.ValueOf(cfg)).ExecuteContext(ctx)
39+
return cmd.ExecuteContext(ctx)
3840
}
3941

4042
func (c *Command) cobraCommand(ctx context.Context, cfgv reflect.Value) *cobra.Command {

0 commit comments

Comments
 (0)