Improve OAUTH2 usability - fixes #131

Shorten the URL to be used by the user and automatically use the
returned code by the server. The browser is opened on
`http://(bindaddress)/auth`, and redirected to the actual URL. When
the code is returned it is automatically inserted, instead of
requiring a copy+paste.

This is also a workaround for the "open" package, which escapes "&"
wrongly on Windows, so the opened URL's are invalid.
This commit is contained in:
klauspost 2015-09-11 14:26:51 +02:00 committed by Nick Craig-Wood
parent 073d112204
commit 807d4a3c00
2 changed files with 43 additions and 14 deletions

View file

@ -42,10 +42,10 @@ client_id>
Amazon Application Client Secret - leave blank to use rclone's. Amazon Application Client Secret - leave blank to use rclone's.
client_secret> client_secret>
Remote config Remote config
If your browser doesn't open automatically go to the following link If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth
https://www.amazon.com/ap/oa?client_id=amzn1.application-oa2-client.xxxxxxxxxxxxxxx&redirect_uri=http%3A%2F%2F127.0.0.1%3A53682%2F&response_type=code&scope=clouddrive%3Aread_all+clouddrive%3Awrite&state=xxxxxxxxxxxxxxxxx Log in and authorize rclone for access
Log in, then cut and paste the token that is returned from the browser here Waiting for code...
Enter verification code> xxxxxxxxxxxxxxxxxxxx Got code
-------------------- --------------------
[remote] [remote]
client_id = client_id =
@ -59,8 +59,8 @@ y/e/d> y
``` ```
Note that rclone runs a webserver on your local machine to collect the Note that rclone runs a webserver on your local machine to collect the
token as returned from Amazon. This is only run from the moment it token as returned from Amazon. This only runs from the moment it
opens your browser to the moment you cut and paste the verification opens your browser to the moment you get back the verification
code. This is on `http://127.0.0.1:53682/` and this it may require code. This is on `http://127.0.0.1:53682/` and this it may require
you to unblock it temporarily if you are running a host firewall. you to unblock it temporarily if you are running a host firewall.

View file

@ -162,27 +162,41 @@ func ConfigWithWebserver(name string, config *oauth2.Config, bindAddress string)
return err return err
} }
state := fmt.Sprintf("%x", stateBytes) state := fmt.Sprintf("%x", stateBytes)
authUrl := config.AuthCodeURL(state)
// Prepare webserver // Prepare webserver
server := authServer{ server := authServer{
state: state, state: state,
bindAddress: bindAddress, bindAddress: bindAddress,
authUrl: authUrl,
} }
if bindAddress != "" { if bindAddress != "" {
server.code = make(chan string, 1)
go server.Start() go server.Start()
defer server.Stop() defer server.Stop()
authUrl = "http://" + bindAddress + "/auth"
} }
// Generate a URL for the user to visit for authorization. // Generate a URL for the user to visit for authorization.
authUrl := config.AuthCodeURL(state)
_ = open.Start(authUrl) _ = open.Start(authUrl)
fmt.Printf("If your browser doesn't open automatically go to the following link\n") fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n")
fmt.Printf("%s\n", authUrl) fmt.Printf("Log in and authorize rclone for access\n")
fmt.Printf("Log in, then cut and paste the token that is returned from the browser here\n")
// Read the code, and exchange it for a token. var authCode string
fmt.Printf("Enter verification code> ") if bindAddress != "" {
authCode := fs.ReadLine() // Read the code, and exchange it for a token.
fmt.Printf("Waiting for code...\n")
authCode = <-server.code
if authCode != "" {
fmt.Printf("Got code\n")
} else {
return fmt.Errorf("Failed to get code")
}
} else {
// Read the code, and exchange it for a token.
fmt.Printf("Enter verification code> ")
authCode = fs.ReadLine()
}
token, err := config.Exchange(oauth2.NoContext, authCode) token, err := config.Exchange(oauth2.NoContext, authCode)
if err != nil { if err != nil {
return fmt.Errorf("Failed to get token: %v", err) return fmt.Errorf("Failed to get token: %v", err)
@ -200,6 +214,8 @@ type authServer struct {
state string state string
listener net.Listener listener net.Listener
bindAddress string bindAddress string
code chan string
authUrl string
} }
// startWebServer runs an internal web server to receive config details // startWebServer runs an internal web server to receive config details
@ -214,6 +230,10 @@ func (s *authServer) Start() {
http.Error(w, "", 404) http.Error(w, "", 404)
return return
}) })
mux.HandleFunc("/auth", func(w http.ResponseWriter, req *http.Request) {
http.Redirect(w, req, s.authUrl, 307)
return
})
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fs.Debug(nil, "Received request on auth server") fs.Debug(nil, "Received request on auth server")
code := req.FormValue("code") code := req.FormValue("code")
@ -224,7 +244,12 @@ func (s *authServer) Start() {
fmt.Fprintf(w, "<h1>Failure</h1>\n<p>Auth state doesn't match</p>") fmt.Fprintf(w, "<h1>Failure</h1>\n<p>Auth state doesn't match</p>")
} else { } else {
fs.Debug(nil, "Successfully got code") fs.Debug(nil, "Successfully got code")
fmt.Fprintf(w, "<h1>Success</h1>\n<p>Cut and paste this code into rclone: <code>%s</code></p>", code) if s.code != nil {
fmt.Fprintf(w, "<h1>Success</h1>\n<p>Go back to rclone to continue</p>")
s.code <- code
} else {
fmt.Fprintf(w, "<h1>Success</h1>\n<p>Cut and paste this code into rclone: <code>%s</code></p>", code)
}
} }
return return
} }
@ -244,5 +269,9 @@ func (s *authServer) Start() {
func (s *authServer) Stop() { func (s *authServer) Stop() {
fs.Debug(nil, "Closing auth server") fs.Debug(nil, "Closing auth server")
if s.code != nil {
close(s.code)
s.code = nil
}
_ = s.listener.Close() _ = s.listener.Close()
} }