Skip to content

Commit d16969d

Browse files
Implementation of auth status and auth login with-api-key (#181)
* Implementation of auth status and auth login with-api-key * Point go-sdk to main version
1 parent ef0b996 commit d16969d

File tree

11 files changed

+328
-89
lines changed

11 files changed

+328
-89
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ require (
1717
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249
1818
github.com/oklog/run v1.1.0
1919
github.com/panta/machineid v1.0.2
20-
github.com/signadot/go-sdk v0.3.8-0.20250430123824-06bc5f2b307c
20+
github.com/signadot/go-sdk v0.3.8-0.20250502141929-71adbfb62bd0
2121
github.com/signadot/libconnect v0.1.1-0.20250502144057-7eab70077f7f
2222
github.com/spf13/cobra v1.8.1
2323
github.com/spf13/viper v1.11.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,8 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
331331
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
332332
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
333333
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
334-
github.com/signadot/go-sdk v0.3.8-0.20250430123824-06bc5f2b307c h1:sD42geQA/uDnx4Fuv271cXIyx1HHTrEPGg0r2HpIdBI=
335-
github.com/signadot/go-sdk v0.3.8-0.20250430123824-06bc5f2b307c/go.mod h1:pnXR9BhGedBWjtAZGwGGY94sMgh6VuAbP/4vq4Qw1Fg=
334+
github.com/signadot/go-sdk v0.3.8-0.20250502141929-71adbfb62bd0 h1:ujylkC2g7u05OYm91oa+ovPK8nmjQq7PO39PNexNP3o=
335+
github.com/signadot/go-sdk v0.3.8-0.20250502141929-71adbfb62bd0/go.mod h1:pnXR9BhGedBWjtAZGwGGY94sMgh6VuAbP/4vq4Qw1Fg=
336336
github.com/signadot/libconnect v0.1.1-0.20250502144057-7eab70077f7f h1:V4kQgQsEJhDfh1LYa2LW2SoJLMaoCP2IYnCpIcMuQXc=
337337
github.com/signadot/libconnect v0.1.1-0.20250502144057-7eab70077f7f/go.mod h1:MWfhryOARFnhDnmYGcTmmu+kHJbE6z0dDgKQpxOKVLQ=
338338
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=

internal/auth/auth.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package auth
2+
3+
import (
4+
"time"
5+
6+
"github.com/spf13/viper"
7+
)
8+
9+
type AuthSource string
10+
11+
const (
12+
ConfigAuthSource AuthSource = "config"
13+
KeyringAuthSource AuthSource = "keyring"
14+
)
15+
16+
type Auth struct {
17+
APIKey string `json:"apiKey,omitempty"`
18+
BearerToken string `json:"bearerToken,omitempty"`
19+
OrgName string `json:"orgName"`
20+
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
21+
}
22+
23+
type ResolvedAuth struct {
24+
Auth
25+
Source AuthSource `json:"source"`
26+
}
27+
28+
func ResolveAuth() (*ResolvedAuth, error) {
29+
auth, err := loadAuth()
30+
if err != nil {
31+
return nil, err
32+
}
33+
if auth == nil {
34+
return nil, nil
35+
}
36+
37+
// fall back to config file for org if not defined
38+
if auth.OrgName == "" {
39+
auth.OrgName = viper.GetString("org")
40+
}
41+
return auth, nil
42+
}
43+
44+
func loadAuth() (*ResolvedAuth, error) {
45+
// give precedence to config
46+
apiKey := viper.GetString("api_key")
47+
if apiKey != "" {
48+
return &ResolvedAuth{
49+
Source: ConfigAuthSource,
50+
Auth: Auth{
51+
APIKey: apiKey,
52+
},
53+
}, nil
54+
}
55+
56+
auth, err := GetAuthFromKeyring()
57+
if err != nil {
58+
return nil, err
59+
}
60+
if auth == nil {
61+
return nil, nil
62+
}
63+
return &ResolvedAuth{
64+
Source: KeyringAuthSource,
65+
Auth: *auth,
66+
}, nil
67+
}

internal/auth/keyring.go

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,46 @@
11
package auth
22

3-
import "github.com/zalando/go-keyring"
3+
import (
4+
"encoding/json"
5+
"errors"
6+
7+
"github.com/zalando/go-keyring"
8+
)
49

510
const (
611
keyringService = "signadot-cli"
7-
tokenKey = "token"
8-
orgKey = "org"
12+
authKey = "auth"
913
)
1014

11-
// StoreToken stores the auth token securely in the system keyring
12-
func StoreToken(token string) error {
13-
return keyring.Set(keyringService, tokenKey, token)
14-
}
15-
16-
// GetToken retrieves the auth token from the system keyring
17-
func GetToken() (string, error) {
18-
return keyring.Get(keyringService, tokenKey)
19-
}
20-
21-
// DeleteToken removes the auth token from the system keyring
22-
func DeleteToken() error {
23-
return keyring.Delete(keyringService, tokenKey)
24-
}
25-
26-
func StoreOrg(org string) error {
27-
return keyring.Set(keyringService, orgKey, org)
15+
func StoreAuthInKeyring(auth *Auth) error {
16+
authJson, err := json.Marshal(auth)
17+
if err != nil {
18+
return err
19+
}
20+
return keyring.Set(keyringService, authKey, string(authJson))
2821
}
2922

30-
func GetOrg() (string, error) {
31-
return keyring.Get(keyringService, orgKey)
23+
func GetAuthFromKeyring() (*Auth, error) {
24+
// read the auth from keyring
25+
authJson, err := keyring.Get(keyringService, authKey)
26+
if err != nil {
27+
if errors.Is(err, keyring.ErrNotFound) {
28+
return nil, nil
29+
}
30+
return nil, err
31+
}
32+
33+
// decode the auth
34+
var auth Auth
35+
err = json.Unmarshal([]byte(authJson), &auth)
36+
if err != nil {
37+
// this is an unlikely state. Remove the entry from the keyring and
38+
// allow the user to log in again.
39+
return nil, DeleteAuthFromKeyring()
40+
}
41+
return &auth, nil
3242
}
3343

34-
func DeleteOrg() error {
35-
return keyring.Delete(keyringService, orgKey)
44+
func DeleteAuthFromKeyring() error {
45+
return keyring.Delete(keyringService, authKey)
3646
}

internal/command/auth/auth.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ func New(api *config.API) *cobra.Command {
1616
// Subcommands
1717
cmd.AddCommand(
1818
newLogin(cfg),
19+
newStatus(cfg),
1920
newLogout(cfg),
2021
)
2122

internal/command/auth/login.go

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package auth
22

33
import (
4+
"errors"
45
"fmt"
56
"io"
67
"time"
@@ -10,6 +11,7 @@ import (
1011
"github.com/signadot/cli/internal/config"
1112
"github.com/signadot/cli/internal/spinner"
1213
sdkauth "github.com/signadot/go-sdk/client/auth"
14+
"github.com/signadot/go-sdk/client/orgs"
1315
"github.com/signadot/go-sdk/models"
1416
"github.com/spf13/cobra"
1517
)
@@ -24,12 +26,64 @@ func newLogin(cfg *config.Auth) *cobra.Command {
2426
return runLogin(loginCfg, cmd.OutOrStdout())
2527
},
2628
}
29+
loginCfg.AddFlags(cmd)
2730

2831
return cmd
2932
}
3033

3134
func runLogin(cfg *config.AuthLogin, out io.Writer) error {
32-
if err := cfg.UnauthInitAPIConfig(); err != nil {
35+
var err error
36+
if cfg.WithAPIKey != "" {
37+
err = apiKeyLogin(cfg, out)
38+
} else {
39+
err = bearerTokenLogin(cfg, out)
40+
}
41+
if err != nil {
42+
return err
43+
}
44+
45+
green := color.New(color.FgGreen).SprintFunc()
46+
fmt.Fprintf(out, "%s Successfully logged in\n", green("✓"))
47+
return nil
48+
}
49+
50+
func apiKeyLogin(cfg *config.AuthLogin, out io.Writer) error {
51+
// init the API client with the provided api key
52+
if err := cfg.InitAPIConfigWithApiKey(cfg.WithAPIKey); err != nil {
53+
return err
54+
}
55+
56+
spin := spinner.Start(out, "Checking provided API key")
57+
defer spin.Stop()
58+
59+
// resolve the org from the api key
60+
res, err := cfg.Client.Orgs.GetOrgName(&orgs.GetOrgNameParams{}, nil)
61+
if err != nil {
62+
spin.StopFail()
63+
return err
64+
}
65+
orgInfo := res.Payload
66+
if len(orgInfo.Orgs) == 0 {
67+
spin.StopFail()
68+
return errors.New("could not resolve org from API key")
69+
}
70+
org := orgInfo.Orgs[0]
71+
72+
// store the auth info
73+
err = auth.StoreAuthInKeyring(&auth.Auth{
74+
APIKey: cfg.WithAPIKey,
75+
OrgName: org.Name,
76+
})
77+
if err != nil {
78+
spin.StopFail()
79+
return fmt.Errorf("failed to store auth info: %w", err)
80+
}
81+
return nil
82+
}
83+
84+
func bearerTokenLogin(cfg *config.AuthLogin, out io.Writer) error {
85+
// init an unauthirezed API client
86+
if err := cfg.InitUnauthAPIConfig(); err != nil {
3387
return err
3488
}
3589

@@ -45,16 +99,16 @@ func runLogin(cfg *config.AuthLogin, out io.Writer) error {
4599
return err
46100
}
47101

48-
// store the access token and org name
49-
if err := auth.StoreToken(token.AccessToken); err != nil {
50-
return fmt.Errorf("failed to store access token: %w", err)
51-
}
52-
if err := auth.StoreOrg(token.OrgName); err != nil {
53-
return fmt.Errorf("failed to store org name: %w", err)
102+
// store the auth info
103+
expiresAt := time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
104+
err = auth.StoreAuthInKeyring(&auth.Auth{
105+
BearerToken: token.AccessToken,
106+
OrgName: token.OrgName,
107+
ExpiresAt: &expiresAt,
108+
})
109+
if err != nil {
110+
return fmt.Errorf("failed to store auth info: %w", err)
54111
}
55-
56-
green := color.New(color.FgGreen).SprintFunc()
57-
fmt.Fprintf(out, "%s Successfully logged in\n", green("✓"))
58112
return nil
59113
}
60114

@@ -85,6 +139,7 @@ func waitForUserAuth(cfg *config.AuthLogin, out io.Writer,
85139

86140
res, err := cfg.Client.Auth.AuthDeviceGetToken(param)
87141
if err != nil {
142+
spin.StopFail()
88143
return nil, fmt.Errorf("couldn't get device token: %w", err)
89144
}
90145
token := res.Payload

internal/command/auth/logout.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/signadot/cli/internal/auth"
1010
"github.com/signadot/cli/internal/config"
1111
"github.com/spf13/cobra"
12-
"github.com/zalando/go-keyring"
1312
)
1413

1514
func newLogout(cfg *config.Auth) *cobra.Command {
@@ -27,11 +26,21 @@ func newLogout(cfg *config.Auth) *cobra.Command {
2726
}
2827

2928
func runLogout(cfg *config.AuthLogout, out io.Writer) error {
30-
if err := auth.DeleteToken(); err != nil {
31-
if errors.Is(err, keyring.ErrNotFound) {
32-
return errors.New("you are already logged out")
33-
}
34-
return fmt.Errorf("failed to delete token: %w", err)
29+
authInfo, err := auth.ResolveAuth()
30+
if err != nil {
31+
return fmt.Errorf("could not resolve auth: %w", err)
32+
}
33+
if authInfo == nil {
34+
return errors.New("You are already logged out.")
35+
}
36+
if authInfo.Source == auth.ConfigAuthSource {
37+
return errors.New(`You are currently logged in using an API key specified via a configuration file
38+
or environment variable. To log out, you must manually unset the environment variable
39+
or remove the API key from the configuration file.`)
40+
}
41+
42+
if err := auth.DeleteAuthFromKeyring(); err != nil {
43+
return fmt.Errorf("failed to delete auth info: %w", err)
3544
}
3645

3746
green := color.New(color.FgGreen).SprintFunc()

0 commit comments

Comments
 (0)