diff --git a/.golangci.yml b/.golangci.yml index 1525df65..ad57114a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -45,7 +45,7 @@ run: - validate.go linters: - enable-all: true + enable-all: false disable: - prealloc - dupl \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go index 615b2077..87c60a7f 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -44,6 +44,87 @@ import ( "github.com/pkg/errors" ) +type DeviceCode struct { + DeviceCode string `json:"device_code"` + UserCode string `json:"user_code"` + VerificationURI string `json:"verification_uri"` + ExpiresIn int `json:"expires_in"` + Interval int `json:"interval"` + VerificationURIComplete string `json:"verification_uri_complete"` +} + +func StartDeviceAuth(authURL, clientID string) (data DeviceCode, err error) { + url := authURL + "/oauth/device/code" + + payload := strings.NewReader("client_id=" + clientID + "&audience=https://api.arduino.cc") + + req, err := http.NewRequest("POST", url, payload) + if err != nil { + return data, err + } + + req.Header.Add("content-type", "application/x-www-form-urlencoded") + + res, err := http.DefaultClient.Do(req) + if err != nil { + return data, err + } + + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return data, err + } + + err = json.Unmarshal(body, &data) + if err != nil { + return data, err + } + + return data, nil +} + +func CheckDeviceAuth(authURL, clientID, deviceCode string) (token string, err error) { + url := authURL + "/oauth/token" + + payload := strings.NewReader("grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=" + deviceCode + "&client_id=" + clientID) + + req, err := http.NewRequest("POST", url, payload) + if err != nil { + return token, err + } + + req.Header.Add("content-type", "application/x-www-form-urlencoded") + + res, err := http.DefaultClient.Do(req) + if err != nil { + return token, err + } + + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return token, err + } + + if res.StatusCode != 200 { + return token, errors.New(string(body)) + } + + data := struct { + AccessToken string `json:"access_token"` + ExpiresIn int `json:"expires_in"` + TokenType string `json:"token_type"` + }{} + + err = json.Unmarshal(body, &data) + if err != nil { + return token, err + } + + return data.AccessToken, nil +} + // Config contains the variables you may want to change type Config struct { // CodeURL is the endpoint to redirect to obtain a code diff --git a/install.go b/install.go index d197a4b0..abe5672f 100644 --- a/install.go +++ b/install.go @@ -20,6 +20,7 @@ package main import ( "bytes" + "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -40,7 +41,7 @@ import ( "time" "github.com/arduino/arduino-connector/auth" - "github.com/eclipse/paho.mqtt.golang" + mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/facchinm/service" "github.com/kardianos/osext" "github.com/pkg/errors" @@ -64,8 +65,8 @@ func register(config Config, configFile, token string) { // Request token var err error if token == "" { - token, err = askCredentials(config.AuthURL) - check(err, "AskCredentials") + token, err = deviceAuth(config.AuthURL, config.AuthClientID) + check(err, "deviceAuth") } // Generate a Private Key and CSR @@ -142,6 +143,40 @@ func registerDeviceViaMQTT(config Config) { } +// Implements Auth0 device authentication flow: https://auth0.com/docs/flows/guides/device-auth/call-api-device-auth +func deviceAuth(authURL, clientID string) (token string, err error) { + code, err := auth.StartDeviceAuth(authURL, clientID) + if err != nil { + return "", err + } + + fmt.Printf("Go to %s and confirm authentication\n", code.VerificationURIComplete) + + ticker := time.NewTicker(10 * time.Second) + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + + // Loop until the user authenticated or the timeout hits +Loop: + for { + select { + case <-ctx.Done(): + break Loop + case <-ticker.C: + var err error + token, err = auth.CheckDeviceAuth(authURL, clientID, code.DeviceCode) + if err == nil { + cancel() + } + } + } + + ticker.Stop() + cancel() + + return token, nil +} + func askCredentials(authURL string) (token string, err error) { var user, pass string fmt.Println("Insert your arduino username") diff --git a/main.go b/main.go index 8e2721dc..eead6205 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ import ( "time" docker "github.com/docker/docker/client" - "github.com/eclipse/paho.mqtt.golang" + mqtt "github.com/eclipse/paho.mqtt.golang" "github.com/fsnotify/fsnotify" "github.com/hpcloud/tail" "github.com/namsral/flag" @@ -59,6 +59,7 @@ type Config struct { HTTPSProxy string ALLProxy string AuthURL string + AuthClientID string APIURL string updateURL string appName string @@ -76,6 +77,7 @@ func (c Config) String() string { out += "https_proxy=" + c.HTTPSProxy + "\r\n" out += "all_proxy=" + c.ALLProxy + "\r\n" out += "authurl=" + c.AuthURL + "\r\n" + out += "auth_client_id=" + c.AuthClientID + "\r\n" out += "apiurl=" + c.APIURL + "\r\n" out += "cert_path=" + c.CertPath + "\r\n" out += "sketches_path=" + c.SketchesPath + "\r\n" @@ -108,7 +110,7 @@ func main() { flag.StringVar(&config.HTTPProxy, "http_proxy", "", "URL of HTTP proxy to use") flag.StringVar(&config.HTTPSProxy, "https_proxy", "", "URL of HTTPS proxy to use") flag.StringVar(&config.ALLProxy, "all_proxy", "", "URL of SOCKS proxy to use") - flag.StringVar(&config.AuthURL, "authurl", "https://hydra.arduino.cc", "Url of authentication server") + flag.StringVar(&config.AuthURL, "authurl", "https://login.arduino.cc", "Url of authentication server") flag.StringVar(&config.APIURL, "apiurl", "https://api2.arduino.cc", "Url of api server") flag.BoolVar(&config.CheckRoFs, "check_ro_fs", false, "Check for Read Only file system and remount if necessary") flag.BoolVar(&debugMqtt, "debug-mqtt", false, "Output all received/sent messages") @@ -117,6 +119,14 @@ func main() { flag.Parse() + if config.AuthURL == "https://login.oniudra.cc" { + config.AuthClientID = "ks1R298bA8IQnG4p6dPlbdEIXF6Kt1Lu" + } + + if config.AuthURL == "https://login.arduino.cc" { + config.AuthClientID = "QGdLCWFA4uQdbRE2NOFhUI8bnXWMZhCK" + } + if *configFile == "" { *configFile = defaultConfigFile } @@ -128,7 +138,7 @@ func main() { check(err, "CreateService") if *doLogin { - token, err := askCredentials(config.AuthURL) + token, err := deviceAuth(config.AuthURL, config.AuthClientID) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1)