Skip to content

Commit d1d71bf

Browse files
authored
Merge pull request #178 from overmindtech/device-authz
Use Device Authorization flow
2 parents f53b002 + 1085746 commit d1d71bf

File tree

1 file changed

+30
-88
lines changed

1 file changed

+30
-88
lines changed

cmd/root.go

Lines changed: 30 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ import (
77
"encoding/json"
88
"errors"
99
"fmt"
10-
"net/http"
1110
"net/url"
1211
"os"
1312
"path"
1413
"path/filepath"
1514
"strings"
16-
"time"
1715

1816
"connectrpc.com/connect"
1917
"github.com/google/uuid"
@@ -26,9 +24,6 @@ import (
2624
"golang.org/x/oauth2"
2725
)
2826

29-
const Auth0ClientId = "j3LylZtIosVPZtouKI8WuVHmE6Lluva1"
30-
const Auth0Domain = "om-prod.eu.auth0.com"
31-
3227
var logLevel string
3328

3429
//go:generate sh -c "echo -n $(git describe --tags --long) > commit.txt"
@@ -147,6 +142,7 @@ func readLocalToken(homeDir string, expectedScopes []string) (string, []string,
147142
}
148143
}
149144

145+
log.Debugf("Using local token from %v", path)
150146
return token.AccessToken, currentScopes, nil
151147
}
152148

@@ -204,92 +200,28 @@ func ensureToken(ctx context.Context, requiredScopes []string) (context.Context,
204200
// keep replacing it
205201
requestScopes := append(requiredScopes, localScopes...)
206202

207-
// Authenticate using the oauth resource owner password flow
203+
// Authenticate using the oauth device authorization flow
208204
config := oauth2.Config{
209-
ClientID: Auth0ClientId,
210-
Scopes: requestScopes,
205+
ClientID: viper.GetString("cli-auth0-client-id"),
211206
Endpoint: oauth2.Endpoint{
212-
AuthURL: fmt.Sprintf("https://%v/authorize", Auth0Domain),
213-
TokenURL: fmt.Sprintf("https://%v/oauth/token", Auth0Domain),
207+
AuthURL: fmt.Sprintf("https://%v/authorize", viper.GetString("cli-auth0-domain")),
208+
TokenURL: fmt.Sprintf("https://%v/oauth/token", viper.GetString("cli-auth0-domain")),
209+
DeviceAuthURL: fmt.Sprintf("https://%v/oauth/device/code", viper.GetString("cli-auth0-domain")),
214210
},
215-
RedirectURL: "http://127.0.0.1:7837/oauth/callback",
211+
Scopes: requestScopes,
216212
}
217213

218-
tokenChan := make(chan *oauth2.Token, 1)
219-
// create a random token for this exchange
220-
oAuthStateString := uuid.New().String()
221-
222-
// Start the web server to listen for the callback
223-
handler := func(w http.ResponseWriter, r *http.Request) {
224-
ctx := r.Context()
225-
226-
queryParts, err := url.ParseQuery(r.URL.RawQuery)
227-
if err != nil {
228-
log.WithContext(ctx).WithError(err).WithFields(log.Fields{
229-
"url": r.URL,
230-
}).Error("Failed to parse url")
231-
}
232-
233-
// Use the authorization code that is pushed to the redirect
234-
// URL.
235-
code := queryParts["code"][0]
236-
log.WithContext(ctx).Debugf("Got code: %v", code)
237-
238-
state := queryParts["state"][0]
239-
log.WithContext(ctx).Debugf("Got state: %v", state)
240-
241-
if state != oAuthStateString {
242-
log.WithContext(ctx).Errorf("Invalid state, expected %v, got %v", oAuthStateString, state)
243-
return
244-
}
245-
246-
// Exchange will do the handshake to retrieve the initial access token.
247-
log.WithContext(ctx).Debug("Exchanging code for token")
248-
tok, err := config.Exchange(ctx, code)
249-
if err != nil {
250-
log.WithContext(ctx).Error(err)
251-
return
252-
}
253-
log.WithContext(ctx).Debug("Got token")
254-
255-
tokenChan <- tok
256-
257-
// show success page
258-
msg := "<p><strong>Success!</strong></p>"
259-
msg = msg + "<p>You are authenticated and can now return to the CLI.</p>"
260-
fmt.Fprint(w, msg)
214+
deviceCode, err := config.DeviceAuth(ctx, oauth2.SetAuthURLParam("audience", "https://api.overmind.tech"))
215+
if err != nil {
216+
return ctx, fmt.Errorf("error getting device code: %w", err)
261217
}
262218

263-
audienceOption := oauth2.SetAuthURLParam("audience", "https://api.overmind.tech")
264-
265-
u := config.AuthCodeURL(oAuthStateString, oauth2.AccessTypeOnline, audienceOption)
266-
log.WithContext(ctx).Infof("Follow this link to authenticate: %v", Underline.TextStyle(u))
219+
fmt.Printf("Go to %v and verify this code: %v\n", deviceCode.VerificationURIComplete, deviceCode.UserCode)
267220

268-
// Start the webserver
269-
log.WithContext(ctx).Trace("Starting webserver to listen for callback, press Ctrl+C to cancel")
270-
srv := &http.Server{Addr: ":7837", ReadHeaderTimeout: 30 * time.Second}
271-
http.HandleFunc("/oauth/callback", handler)
272-
273-
go func() {
274-
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
275-
// unexpected error. port in use?
276-
log.WithContext(ctx).Errorf("HTTP Server error: %v", err)
277-
}
278-
}()
279-
280-
// Wait for the token or cancel
281-
var token *oauth2.Token
282-
select {
283-
case token = <-tokenChan:
284-
// Keep working
285-
case <-ctx.Done():
286-
return ctx, ctx.Err()
287-
}
288-
289-
// Stop the server
290-
err = srv.Shutdown(ctx)
221+
token, err := config.DeviceAccessToken(ctx, deviceCode)
291222
if err != nil {
292-
log.WithContext(ctx).WithError(err).Warn("failed to shutdown auth callback server, but continuing anyway")
223+
fmt.Printf(": %v\n", err)
224+
return ctx, fmt.Errorf("Error exchanging Device Code for for access token: %w", err)
293225
}
294226

295227
// Check that we actually got the claims we asked for. If you don't have
@@ -438,15 +370,17 @@ func init() {
438370
log.WithError(err).Fatal("could not bind api key to env")
439371
}
440372

441-
// tracing
373+
// internal configs
374+
rootCmd.PersistentFlags().String("cli-auth0-client-id", "QMfjMww3x4QTpeXiuRtMV3JIQkx6mZa4", "OAuth Client ID to use when connecting with auth0")
375+
rootCmd.PersistentFlags().String("cli-auth0-domain", "om-prod.eu.auth0.com", "Auth0 domain to connect to")
442376
rootCmd.PersistentFlags().String("honeycomb-api-key", "", "If specified, configures opentelemetry libraries to submit traces to honeycomb. This requires --otel to be set.")
443-
// Mark this as hidden. This means that it will still be parsed of supplied,
377+
378+
// Mark these as hidden. This means that it will still be parsed of supplied,
444379
// and we will still look for it in the environment, but it won't be shown
445380
// in the help
446-
err = rootCmd.PersistentFlags().MarkHidden("honeycomb-api-key")
447-
if err != nil {
448-
log.WithError(err).Fatal("could not mark `honeycomb-api-key` flag as hidden")
449-
}
381+
must(rootCmd.PersistentFlags().MarkHidden("cli-auth0-client-id"))
382+
must(rootCmd.PersistentFlags().MarkHidden("cli-auth0-domain"))
383+
must(rootCmd.PersistentFlags().MarkHidden("honeycomb-api-key"))
450384

451385
// Create groups
452386
rootCmd.AddGroup(&cobra.Group{
@@ -502,3 +436,11 @@ func initConfig() {
502436
viper.SetEnvKeyReplacer(replacer)
503437
viper.AutomaticEnv() // read in environment variables that match
504438
}
439+
440+
// must panics if the passed in error is not nil
441+
// use this for init-time error checking of viper/cobra stuff that sometimes errors if the flag does not exist
442+
func must(err error) {
443+
if err != nil {
444+
panic(fmt.Errorf("error initialising: %w", err))
445+
}
446+
}

0 commit comments

Comments
 (0)