Docs, code cleanups.

This commit is contained in:
Burcu Dogan 2014-08-12 19:50:34 -07:00
parent eb7270d354
commit a9dc52b3d3
2 changed files with 31 additions and 14 deletions

View File

@ -14,16 +14,17 @@ import (
"mime" "mime"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
) )
type tokenRespBody struct { type tokenRespBody struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
ExpiresIn time.Duration `json:"expires_in"` ExpiresIn int64 `json:"expires_in"` // in seconds
IdToken string `json:"id_token"` IdToken string `json:"id_token"`
} }
// TokenFetcher refreshes or fetches a new access token from the // TokenFetcher refreshes or fetches a new access token from the
@ -63,7 +64,15 @@ type Options struct {
// Optional, identifies the level of access being requested. // Optional, identifies the level of access being requested.
Scopes []string `json:"scopes"` Scopes []string `json:"scopes"`
// Optional, "online" (default) or "offline", no refresh token if "online" // AccessType is an OAuth extension that gets sent as the
// "access_type" field in the URL from AuthCodeURL.
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
// It may be "online" (the default) or "offline".
// If your application needs to refresh access tokens when the
// user is not present at the browser, then use offline. This
// will result in your application obtaining a refresh token
// the first time your application exchanges an authorization
// code for a user.
AccessType string `json:"omit"` AccessType string `json:"omit"`
// ApprovalPrompt indicates whether the user should be // ApprovalPrompt indicates whether the user should be
@ -187,8 +196,9 @@ func (c *Config) validate() error {
func (c *Config) Exchange(exchangeCode string) (*Token, error) { func (c *Config) Exchange(exchangeCode string) (*Token, error) {
token := &Token{} token := &Token{}
vals := url.Values{ vals := url.Values{
"grant_type": {"authorization_code"}, "grant_type": {"authorization_code"},
"code": {exchangeCode}, "client_secret": {c.opts.ClientSecret},
"code": {exchangeCode},
} }
if len(c.opts.Scopes) != 0 { if len(c.opts.Scopes) != 0 {
vals.Set("scope", strings.Join(c.opts.Scopes, " ")) vals.Set("scope", strings.Join(c.opts.Scopes, " "))
@ -203,9 +213,15 @@ func (c *Config) Exchange(exchangeCode string) (*Token, error) {
return token, nil return token, nil
} }
// updateToken mutates both tok and v.
func (c *Config) updateToken(tok *Token, v url.Values) error { func (c *Config) updateToken(tok *Token, v url.Values) error {
v.Set("client_id", c.opts.ClientID) v.Set("client_id", c.opts.ClientID)
v.Set("client_secret", c.opts.ClientSecret) // Note that we're not setting v's client_secret to t.ClientSecret, due
// to https://code.google.com/p/goauth2/issues/detail?id=31
// Reddit only accepts client_secret in Authorization header.
// Dropbox accepts either, but not both.
// The spec requires servers to always support the Authorization header,
// so that's all we use.
r, err := http.DefaultClient.PostForm(c.tokenURL.String(), v) r, err := http.DefaultClient.PostForm(c.tokenURL.String(), v)
if err != nil { if err != nil {
return err return err
@ -231,15 +247,12 @@ func (c *Config) updateToken(tok *Token, v url.Values) error {
resp.AccessToken = vals.Get("access_token") resp.AccessToken = vals.Get("access_token")
resp.TokenType = vals.Get("token_type") resp.TokenType = vals.Get("token_type")
resp.RefreshToken = vals.Get("refresh_token") resp.RefreshToken = vals.Get("refresh_token")
resp.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s") resp.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
resp.IdToken = vals.Get("id_token") resp.IdToken = vals.Get("id_token")
default: default:
if err = json.NewDecoder(r.Body).Decode(&resp); err != nil { if err = json.NewDecoder(r.Body).Decode(&resp); err != nil {
return err return err
} }
// The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above,
// so compensate here.
resp.ExpiresIn *= time.Second
} }
tok.AccessToken = resp.AccessToken tok.AccessToken = resp.AccessToken
tok.TokenType = resp.TokenType tok.TokenType = resp.TokenType
@ -250,7 +263,7 @@ func (c *Config) updateToken(tok *Token, v url.Values) error {
if resp.ExpiresIn == 0 { if resp.ExpiresIn == 0 {
tok.Expiry = time.Time{} tok.Expiry = time.Time{}
} else { } else {
tok.Expiry = time.Now().Add(resp.ExpiresIn) tok.Expiry = time.Now().Add(time.Duration(resp.ExpiresIn) * time.Second)
} }
if resp.IdToken != "" { if resp.IdToken != "" {
if tok.Extra == nil { if tok.Extra == nil {

View File

@ -30,6 +30,10 @@ type Token struct {
// The remaining lifetime of the access token. // The remaining lifetime of the access token.
Expiry time.Time `json:"expiry,omitempty"` Expiry time.Time `json:"expiry,omitempty"`
// Extra optionally contains extra metadata from the server
// when updating a token. The only current key that may be
// populated is "id_token". It may be nil and will be
// initialized as needed.
Extra map[string]string `json:"extra,omitempty"` Extra map[string]string `json:"extra,omitempty"`
// JWT related fields // JWT related fields