Relocated the logic for authorizing clients into a separate package that the tokens server now depends on. Moving this helped me separate concerns. I removed a few top-level variables and tried to write more pure versions of the authorization functions to avoid leaking Monzo-specific details.
		
			
				
	
	
		
			101 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			101 lines
		
	
	
	
		
			3.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package auth
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // Dependencies
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| import (
 | |
| 	"encoding/json"
 | |
| 	"fmt"
 | |
| 	"log"
 | |
| 	"net/http"
 | |
| 	"net/url"
 | |
| 	"os"
 | |
| 	"os/exec"
 | |
| 	"utils"
 | |
| )
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // Constants
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| var (
 | |
| 	BROWSER      = os.Getenv("BROWSER")
 | |
| 	REDIRECT_URI = "http://localhost:8080/authorization-code"
 | |
| )
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // Types
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| // This is the response returned from Monzo when we exchange our authorization
 | |
| // code for an access token. While Monzo returns additional fields, I'm only
 | |
| // interested in AccessToken and RefreshToken.
 | |
| type accessTokenResponse struct {
 | |
| 	AccessToken  string `json:"access_token"`
 | |
| 	RefreshToken string `json:"refresh_token"`
 | |
| 	ExpiresIn    int    `json:"expires_in"`
 | |
| }
 | |
| 
 | |
| type Tokens struct {
 | |
| 	AccessToken  string
 | |
| 	RefreshToken string
 | |
| 	ExpiresIn    int
 | |
| }
 | |
| 
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| // Functions
 | |
| ////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| // Returns the access token and refresh tokens for the Monzo API.
 | |
| func GetTokensFromAuthCode(authCode string, clientID string, clientSecret string) *Tokens {
 | |
| 	res, err := http.PostForm("https://api.monzo.com/oauth2/token", url.Values{
 | |
| 		"grant_type":    {"authorization_code"},
 | |
| 		"client_id":     {clientID},
 | |
| 		"client_secret": {clientSecret},
 | |
| 		"redirect_uri":  {REDIRECT_URI},
 | |
| 		"code":          {authCode},
 | |
| 	})
 | |
| 	utils.FailOn(err)
 | |
| 	defer res.Body.Close()
 | |
| 	payload := &accessTokenResponse{}
 | |
| 	json.NewDecoder(res.Body).Decode(payload)
 | |
| 
 | |
| 	return &Tokens{payload.AccessToken, payload.RefreshToken, payload.ExpiresIn}
 | |
| }
 | |
| 
 | |
| // Open a web browser to allow the user to authorize this application. Return
 | |
| // the authorization code sent from Monzo.
 | |
| func GetAuthCode(clientID string) string {
 | |
| 	// TODO(wpcarro): Consider generating a random string for the state when the
 | |
| 	// application starts instead of hardcoding it here.
 | |
| 	state := "xyz123"
 | |
| 	url := fmt.Sprintf(
 | |
| 		"https://auth.monzo.com/?client_id=%s&redirect_uri=%s&response_type=code&state=%s",
 | |
| 		clientID, REDIRECT_URI, state)
 | |
| 	exec.Command(BROWSER, url).Start()
 | |
| 
 | |
| 	authCode := make(chan string)
 | |
| 	go func() {
 | |
| 		log.Fatal(http.ListenAndServe(":8080",
 | |
| 			http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | |
| 				// 1. Get authorization code from Monzo.
 | |
| 				if req.URL.Path == "/authorization-code" {
 | |
| 					params := req.URL.Query()
 | |
| 					reqState := params["state"][0]
 | |
| 					code := params["code"][0]
 | |
| 
 | |
| 					if reqState != state {
 | |
| 						log.Fatalf("Value for state returned by Monzo does not equal our state. %s != %s", reqState, state)
 | |
| 					}
 | |
| 					authCode <- code
 | |
| 
 | |
| 					fmt.Fprintf(w, "Authorized!")
 | |
| 				} else {
 | |
| 					log.Printf("Unhandled request: %v\n", *req)
 | |
| 				}
 | |
| 			})))
 | |
| 	}()
 | |
| 	result := <-authCode
 | |
| 	return result
 | |
| }
 |