-
Notifications
You must be signed in to change notification settings - Fork 29
Expand file tree
/
Copy pathoauth_flow.go
More file actions
112 lines (96 loc) · 3.36 KB
/
oauth_flow.go
File metadata and controls
112 lines (96 loc) · 3.36 KB
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
package auth
import (
"fmt"
"os"
"os/exec"
"runtime"
"github.com/algolia/cli/api/dashboard"
"github.com/algolia/cli/pkg/iostreams"
)
// DefaultOAuthClientID is injected at build time via ldflags.
// Override with ALGOLIA_OAUTH_CLIENT_ID environment variable for local development.
var DefaultOAuthClientID = ""
// OAuthClientID returns the OAuth client ID, preferring the ALGOLIA_OAUTH_CLIENT_ID
// environment variable over the compiled-in default (set via ldflags).
func OAuthClientID() string {
if v := os.Getenv("ALGOLIA_OAUTH_CLIENT_ID"); v != "" {
return v
}
if DefaultOAuthClientID == "" {
fmt.Fprintln(os.Stderr, "fatal: ALGOLIA_OAUTH_CLIENT_ID is not set and no default was compiled in")
os.Exit(1)
}
return DefaultOAuthClientID
}
// RunOAuth runs the OAuth PKCE flow with a local callback server and returns
// a valid access token. A local HTTP server is started on a random port to
// receive the authorization code via redirect - no copy-paste required.
//
// When openBrowser is true the authorize URL is opened automatically;
// otherwise only the URL is printed (useful when the browser can't be
// launched, e.g. SSH / containers).
//
// If signup is true the browser opens to the sign-up page.
func RunOAuth(io *iostreams.IOStreams, client *dashboard.Client, signup, openBrowser bool) (string, error) {
cs := io.ColorScheme()
redirectURI, resultCh, err := StartCallbackServer()
if err != nil {
return "", err
}
codeVerifier, err := GenerateCodeVerifier()
if err != nil {
return "", fmt.Errorf("failed to generate PKCE verifier: %w", err)
}
codeChallenge := CodeChallenge(codeVerifier)
var authorizeURL string
if signup {
authorizeURL = client.SignupAuthorizeURL(codeChallenge, redirectURI)
} else {
authorizeURL = client.AuthorizeURL(codeChallenge, redirectURI)
}
if openBrowser {
if signup {
fmt.Fprintf(io.Out, "Opening browser to create an account...\n")
} else {
fmt.Fprintf(io.Out, "Opening browser to sign in...\n")
}
fmt.Fprintf(io.Out, "If the browser doesn't open, visit:\n %s\n\n", cs.Bold(authorizeURL))
_ = OpenBrowser(authorizeURL)
} else {
fmt.Fprintf(io.Out, "Open this URL in your browser to authenticate:\n\n %s\n\n", cs.Bold(authorizeURL))
}
fmt.Fprintf(io.Out, "Waiting for authentication...\n")
cbResult := <-resultCh
if cbResult.Error != "" {
return "", fmt.Errorf("authorization failed: %s", cbResult.Error)
}
if cbResult.Code == "" {
return "", fmt.Errorf("no authorization code received")
}
io.StartProgressIndicatorWithLabel("Exchanging code for tokens")
tokenResp, err := client.AuthorizationCodeGrant(cbResult.Code, codeVerifier, redirectURI)
io.StopProgressIndicator()
if err != nil {
return "", err
}
if tokenResp.User != nil {
fmt.Fprintf(io.Out, "%s Signed in as %s\n", cs.SuccessIcon(), cs.Bold(tokenResp.User.Email))
}
if err := SaveToken(tokenResp); err != nil {
fmt.Fprintf(io.ErrOut, "%s Could not save auth token: %s\n", cs.WarningIcon(), err)
}
return tokenResp.AccessToken, nil
}
// OpenBrowser opens the given URL in the user's default browser.
func OpenBrowser(url string) error {
switch runtime.GOOS {
case "darwin":
return exec.Command("open", url).Start()
case "linux":
return exec.Command("xdg-open", url).Start()
case "windows":
return exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
default:
return fmt.Errorf("unsupported platform")
}
}