Created
April 20, 2017 14:39
-
-
Save bbengfort/ee6d3bda44b8cc8d10a26f66be9ced70 to your computer and use it in GitHub Desktop.
OAuth2 token cacheing and management
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package daytimer | |
import ( | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"net/url" | |
"os" | |
"os/exec" | |
"os/user" | |
"path/filepath" | |
"runtime" | |
calendar "google.golang.org/api/calendar/v3" | |
"golang.org/x/oauth2" | |
"golang.org/x/oauth2/google" | |
) | |
// Authentication is a wrapper for the OAuth token authentication process, | |
// reading and writing the token from a credentials file on disk. It is meant | |
// to perform 100% of the workflow of authentication. | |
type Authentication struct { | |
cachePath string // the path to the token cache file on disk | |
configPath string // the path to the client_secret.json file on disk | |
token *oauth2.Token // the oauth token cached in the cache file | |
} | |
// Token returns the authentication token by first looking on disk for the | |
// cache, and if it doesn't exist by executing the authentication. | |
func (auth *Authentication) Token() (*oauth2.Token, error) { | |
// Load the token from the cache if it doesn't exist. | |
if auth.token == nil { | |
if err := auth.Load(""); err != nil { | |
// If we cannot load the token from disk, authenticate | |
if err != nil { | |
if err = auth.Authenticate(); err != nil { | |
return nil, err | |
} | |
} | |
} | |
} | |
// Return the token | |
return auth.token, nil | |
} | |
// Authenticate runs an interactive authentication on the command line, | |
// prompting the user to open a brower page and enter an authorization code. | |
// It will then fetch an token via OAuth and cache it as credentials. Note | |
// that this method will overwrite any previously cached token. | |
func (auth *Authentication) Authenticate() error { | |
// Load and create the OAuth2 Configuration | |
config, err := auth.Config() | |
if err != nil { | |
return err | |
} | |
// Compute the URL for the authoerization | |
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) | |
// Notify the user of the web browser. | |
fmt.Println("In order to authenticate, use a browser to authorize daytimer with Google") | |
// Open the web browser | |
switch runtime.GOOS { | |
case "linux": | |
err = exec.Command("xdg-open", authURL).Start() | |
case "windows", "darwin": | |
err = exec.Command("open", authURL).Start() | |
default: | |
err = fmt.Errorf("unsupported platform: %s", runtime.GOOS) | |
} | |
// If we couldn't open the web browser, prompt the user to do it manually. | |
if err != nil { | |
fmt.Printf("Copy and paste the following link: \n%s\n\n", authURL) | |
} | |
// Prompt for the authorization code | |
code, err := Prompt("enter authorization code:") | |
if err != nil { | |
return fmt.Errorf("unable to read authorization code %v", err) | |
} | |
// Perform the exchange for the token | |
token, err := config.Exchange(oauth2.NoContext, code) | |
if err != nil { | |
return fmt.Errorf("unable to retrieve token from web %v", err) | |
} | |
// Cache the token to disk | |
auth.token = token | |
auth.Save("") | |
return nil | |
} | |
// Config loads the client_secret.json from the ConfigPath. It is used both to | |
// create the client for requests as well as to perform authentication. | |
func (auth *Authentication) Config() (*oauth2.Config, error) { | |
path, err := auth.ConfigPath() | |
if err != nil { | |
return nil, err | |
} | |
data, err := ioutil.ReadFile(path) | |
if err != nil { | |
return nil, fmt.Errorf("unable to read client secret file: %v", err) | |
} | |
config, err := google.ConfigFromJSON(data, calendar.CalendarReadonlyScope) | |
if err != nil { | |
return nil, fmt.Errorf("unable to parse client secret file to config: %v", err) | |
} | |
return config, nil | |
} | |
// CachePath computes the path to the credential token file, creating the | |
// directory if necessary and stores it in the authentication struct. | |
func (auth *Authentication) CachePath() (string, error) { | |
if auth.cachePath == "" { | |
// Get the user to look up the user's home directory | |
usr, err := user.Current() | |
if err != nil { | |
return "", err | |
} | |
// Get the hidden credentials directory, making sure it's created | |
cacheDir := filepath.Join(usr.HomeDir, ".daytimer") | |
os.MkdirAll(cacheDir, 0700) | |
// Determine the path to the token cache file | |
cacheFile := url.QueryEscape("credentials.json") | |
auth.cachePath = filepath.Join(cacheDir, cacheFile) | |
} | |
return auth.cachePath, nil | |
} | |
// ConfigPath computes the path to the configuration file, client_secret.json. | |
func (auth *Authentication) ConfigPath() (string, error) { | |
if auth.configPath == "" { | |
// Get the user to look up the user's home directory | |
usr, err := user.Current() | |
if err != nil { | |
return "", err | |
} | |
// Create the path to the default configuration | |
auth.configPath = filepath.Join( | |
usr.HomeDir, ".daytimer", "client_secret.json", | |
) | |
} | |
return auth.configPath, nil | |
} | |
// Load the token from the specified path (and saves the path to the struct). | |
// If path is an empty string then it will load the token from the default | |
// cache path in the home directory. This method returns an error if the token | |
// cannot be loaded from the file. | |
func (auth *Authentication) Load(path string) error { | |
var err error | |
// Get the default cache path or save the specified path | |
if path == "" { | |
path, err = auth.CachePath() | |
if err != nil { | |
return err | |
} | |
} else { | |
auth.cachePath = path | |
} | |
// Open the file at the path | |
f, err := os.Open(path) | |
if err != nil { | |
return fmt.Errorf("could not open cache file at %s: %v", path, err) | |
} | |
defer f.Close() | |
// Decode the JSON token cache | |
auth.token = new(oauth2.Token) | |
if err := json.NewDecoder(f).Decode(auth.token); err != nil { | |
return fmt.Errorf("could not decode token in cache file at %s: %v", path, err) | |
} | |
return nil | |
} | |
// Save the token to the specified path (and save the path to the struct). | |
// If the path is empty, then it will save the path to the current CachePath. | |
func (auth *Authentication) Save(path string) error { | |
var err error | |
// Get the default cache path or save the specified path | |
if path == "" { | |
path, err = auth.CachePath() | |
if err != nil { | |
return err | |
} | |
} else { | |
auth.cachePath = path | |
} | |
// Open the file for writing | |
f, err := os.Create(path) | |
if err != nil { | |
return fmt.Errorf("unable to cache oauth token: %v", err) | |
} | |
defer f.Close() | |
// Encode the token and write to disk | |
if err := json.NewEncoder(f).Encode(auth.token); err != nil { | |
return fmt.Errorf("could not encode oauth token: %v", err) | |
} | |
return nil | |
} | |
// Delete the token file at the given path in order to force a | |
// reauthentication. This method also saves the given path, or if the path is | |
// empty, then it will compute the default CachePath. This method will not | |
// return an error on failure (e.g. if the file does not exist). | |
func (auth *Authentication) Delete(path string) { | |
// Get the default cache path or save the specified path | |
if path == "" { | |
path, _ = auth.CachePath() | |
} else { | |
auth.cachePath = path | |
} | |
// Delete the file at the cache path if it exists | |
os.Remove(path) | |
} |
@jayhuang75 Please follow https://github.com/bbengfort/daytimer/blob/master/README.md
i tried, it works !
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello @bbengfort, Thank you so much for this snippet code, and I try my self and get some error special for the 81 line
code, err := Prompt("enter authorization code:")
Please advise what package you are using? for the Prompt
Thanks