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.
client_secret>
Remote config
If your browser doesn't open automatically go to the following link
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, then cut and paste the token that is returned from the browser here
Enter verification code> xxxxxxxxxxxxxxxxxxxx
If your browser doesn't open automatically go to the following link: http://127.0.0.1:53682/auth
Log in and authorize rclone for access
Waiting for code...
Got code
--------------------
[remote]
client_id =
@ -59,8 +59,8 @@ y/e/d> y
```
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
opens your browser to the moment you cut and paste the verification
token as returned from Amazon. This only runs from the moment it
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
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
}
state := fmt.Sprintf("%x", stateBytes)
authUrl := config.AuthCodeURL(state)
// Prepare webserver
server := authServer{
state: state,
bindAddress: bindAddress,
authUrl: authUrl,
}
if bindAddress != "" {
server.code = make(chan string, 1)
go server.Start()
defer server.Stop()
authUrl = "http://" + bindAddress + "/auth"
}
// Generate a URL for the user to visit for authorization.
authUrl := config.AuthCodeURL(state)
_ = open.Start(authUrl)
fmt.Printf("If your browser doesn't open automatically go to the following link\n")
fmt.Printf("%s\n", authUrl)
fmt.Printf("Log in, then cut and paste the token that is returned from the browser here\n")
fmt.Printf("If your browser doesn't open automatically go to the following link: %s\n")
fmt.Printf("Log in and authorize rclone for access\n")
// Read the code, and exchange it for a token.
fmt.Printf("Enter verification code> ")
authCode := fs.ReadLine()
var authCode string
if bindAddress != "" {
// 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)
if err != nil {
return fmt.Errorf("Failed to get token: %v", err)
@ -200,6 +214,8 @@ type authServer struct {
state string
listener net.Listener
bindAddress string
code chan string
authUrl string
}
// startWebServer runs an internal web server to receive config details
@ -214,6 +230,10 @@ func (s *authServer) Start() {
http.Error(w, "", 404)
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) {
fs.Debug(nil, "Received request on auth server")
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>")
} else {
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
}
@ -244,5 +269,9 @@ func (s *authServer) Start() {
func (s *authServer) Stop() {
fs.Debug(nil, "Closing auth server")
if s.code != nil {
close(s.code)
s.code = nil
}
_ = s.listener.Close()
}