-
Notifications
You must be signed in to change notification settings - Fork 224
/
auth.go
134 lines (104 loc) · 3.09 KB
/
auth.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Package auth implements the auth command chain.
package auth
import (
"context"
"fmt"
"io"
"time"
"github.com/azazeal/pause"
"github.com/briandowns/spinner"
"github.com/pkg/errors"
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
"github.com/superfly/flyctl/iostreams"
"github.com/superfly/flyctl/api"
"github.com/superfly/flyctl/client"
"github.com/superfly/flyctl/internal/command"
"github.com/superfly/flyctl/internal/config"
"github.com/superfly/flyctl/internal/logger"
"github.com/superfly/flyctl/internal/state"
)
// New initializes and returns a new apps Command.
func New() *cobra.Command {
const (
long = `Authenticate with Fly (and logout if you need to).
If you do not have an account, start with the AUTH SIGNUP command.
If you do have an account, begin with the AUTH LOGIN subcommand.
`
short = "Manage authentication"
)
auth := command.New("auth", short, long, nil)
auth.AddCommand(
newWhoAmI(),
newToken(),
newLogin(),
newDocker(),
newLogout(),
newSignup(),
)
return auth
}
func runWebLogin(ctx context.Context, signup bool) error {
auth, err := api.StartCLISessionWebAuth(state.Hostname(ctx), signup)
if err != nil {
return err
}
io := iostreams.FromContext(ctx)
if err := open.Run(auth.AuthURL); err != nil {
fmt.Fprintf(io.ErrOut,
"failed opening browser. Copy the url (%s) into a browser and continue\n",
auth.AuthURL,
)
}
logger := logger.FromContext(ctx)
colorize := io.ColorScheme()
fmt.Fprintf(io.Out, "Opening %s ...\n\n", colorize.Bold(auth.AuthURL))
token, err := waitForCLISession(ctx, logger, io.ErrOut, auth.ID)
switch {
case errors.Is(err, context.DeadlineExceeded):
return errors.New("Login expired, please try again")
case err != nil:
return err
case token == "":
return errors.New("failed to log in, please try again")
}
if err := persistAccessToken(ctx, token); err != nil {
return err
}
client := client.FromToken(token).API()
user, err := client.GetCurrentUser(ctx)
if err != nil {
return fmt.Errorf("failed retrieving current user: %w", err)
}
fmt.Fprintf(io.Out, "successfully logged in as %s\n", colorize.Bold(user.Email))
return nil
}
// TODO: this does NOT break on interrupts
func waitForCLISession(parent context.Context, logger *logger.Logger, w io.Writer, id string) (token string, err error) {
ctx, cancel := context.WithTimeout(parent, 15*time.Minute)
defer cancel()
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
s.Writer = w
s.Prefix = "Waiting for session..."
s.Start()
for ctx.Err() == nil {
if token, err = api.GetAccessTokenForCLISession(ctx, id); err != nil {
logger.Debugf("failed retrieving token: %v", err)
pause.For(ctx, time.Second)
continue
}
logger.Debug("retrieved access token.")
s.FinalMSG = "Waiting for session... Done\n"
s.Stop()
break
}
return
}
func persistAccessToken(ctx context.Context, token string) (err error) {
path := state.ConfigFile(ctx)
if err = config.SetAccessToken(path, token); err != nil {
err = fmt.Errorf("failed persisting %s in %s: %w\n",
config.AccessTokenFileKey, path, err)
}
return
}