<|start_filename|>token.go<|end_filename|>
package oauth2

import (
	"net/url"
	"strconv"
	"strings"
	"time"
)

// Token represents the credentials used to authorize the requests to access
// protected resources on the OAuth 2.0 provider's backend.
//
type Token struct {
	AccessToken  string      `json:"access_token"`            // AccessToken is the token that authorizes and authenticates the requests.
	TokenType    string      `json:"token_type,omitempty"`    // TokenType is the type of token. The Type method returns either this or "Bearer".
	RefreshToken string      `json:"refresh_token,omitempty"` // RefreshToken is a token that's used by the application to refresh the access token if it expires.
	Expiry       time.Time   `json:"expiry,omitempty"`        // Expiry is the expiration time of the access token.
	Raw          interface{} // Raw optionally contains extra metadata from the server when updating a token.
}

// Type returns t.TokenType if non-empty, else "Bearer".
func (t *Token) Type() string {
	switch {
	case strings.EqualFold(t.TokenType, "bearer"):
		return "Bearer"
	case strings.EqualFold(t.TokenType, "mac"):
		return "MAC"
	case strings.EqualFold(t.TokenType, "basic"):
		return "Basic"
	case t.TokenType != "":
		return t.TokenType
	default:
		return "Bearer"
	}
}

// Extra returns an extra field.
// Extra fields are key-value pairs returned by the server as a
// part of the token retrieval response.
func (t *Token) Extra(key string) interface{} {
	switch v := t.Raw.(type) {
	case map[string]interface{}:
		return v[key]

	case url.Values:
		value := v.Get(key)
		s := strings.TrimSpace(value)

		switch strings.Count(s, ".") {
		case 0:
			// Contains no "."; try to parse as int
			i, err := strconv.ParseInt(s, 10, 64)
			if err == nil {
				return i
			}
		case 1:
			// Contains a single "."; try to parse as float
			f, err := strconv.ParseFloat(s, 64)
			if err == nil {
				return f
			}
		}
		return value

	default:
		return nil
	}
}

// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
//
func (t *Token) Valid() bool {
	return t != nil && t.AccessToken != "" && !t.IsExpired()
}

// timeNow is used only in Token.IsExpired, is always time.Now, except some tests.
var timeNow = time.Now

// expiryDelta determines how earlier a token should be considered
// expired than its actual expiration time. It is used to avoid late
// expirations due to client-server time mismatches.
const expiryDelta = 10 * time.Second

// IsExpired reports whether the token is expired.
//
func (t *Token) IsExpired() bool {
	if t.Expiry.IsZero() {
		return false
	}
	return t.Expiry.Round(0).Add(-expiryDelta).Before(timeNow())
}


<|start_filename|>wrapper_test.go<|end_filename|>
package oauth2

import (
	"net/http"
	"testing"
	"time"
)

func TestWrap(t *testing.T) {
	apikey := "Test-Api-Key-123"

	ts := newServer(func(w http.ResponseWriter, r *http.Request) {
		got := r.Header.Get("Authorization")
		want := apikey
		if got != want {
			t.Errorf("Authorization header = %q; want %q", got, want)
		}
		w.WriteHeader(http.StatusOK)
	})
	defer ts.Close()

	c := &http.Client{Timeout: 5 * time.Second}
	wc, err := Wrap("Authorization", apikey, c)

	if err != nil {
		t.Fatal(err)
	}
	resp, err := wc.Get(ts.URL)
	if err != nil {
		t.Fatal(err)
	}
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("status code %#v, want %#v", resp.StatusCode, http.StatusOK)
	}
}


<|start_filename|>token_test.go<|end_filename|>
package oauth2

import (
	"net/url"
	"testing"
	"time"
)

func TestTokenTypeMethod(t *testing.T) {
	f := func(token *Token, want string) {
		t.Helper()

		got := token.Type()
		if got != want {
			t.Errorf("got %v; want %v", got, want)
		}
	}

	f(
		&Token{}, "Bearer",
	)
	f(
		&Token{TokenType: "beAREr"}, "Bearer",
	)
	f(
		&Token{TokenType: "beAREr"}, "Bearer",
	)
	f(
		&Token{TokenType: "basic"}, "Basic",
	)
	f(
		&Token{TokenType: "Basic"}, "Basic",
	)
	f(
		&Token{TokenType: "mac"}, "MAC",
	)
	f(
		&Token{TokenType: "MAC"}, "MAC",
	)
	f(
		&Token{TokenType: "mAc"}, "MAC",
	)
	f(
		&Token{TokenType: "unknown"}, "unknown",
	)
}

func TestTokenExtra(t *testing.T) {
	const wantKey = "extra-key"

	f := func(key string, value, want interface{}) {
		t.Helper()

		extra := map[string]interface{}{
			key: value,
		}
		token := &Token{
			Raw: extra,
		}

		got := token.Extra(wantKey)
		if got != want {
			t.Errorf("Extra(%q) = %q; want %q", key, got, want)
		}
	}

	f("extra-key", "abc", "abc")
	f("extra-key", 123, 123)
	f("extra-key", "", "")
	f("other-key", "def", nil)
}

func TestTokenExpiry(t *testing.T) {
	now := time.Now()
	timeNow = func() time.Time { return now }
	defer func() { timeNow = time.Now }()

	f := func(token *Token, want bool) {
		t.Helper()

		got := token.IsExpired()
		if got != want {
			t.Errorf("got %v; want %v", got, want)
		}
	}

	f(
		&Token{Expiry: now.Add(12 * time.Second)},
		false,
	)
	f(
		&Token{Expiry: now.Add(expiryDelta)},
		false,
	)
	f(
		&Token{Expiry: now.Add(expiryDelta - 1*time.Nanosecond)},
		true,
	)
	f(
		&Token{Expiry: now.Add(-1 * time.Hour)},
		true,
	)
}

func TestExtraValueRetrieval(t *testing.T) {
	kvmap := map[string]string{
		"scope":       "user",
		"token_type":  "bearer",
		"expires_in":  "86400.92",
		"server_time": "1443571905.5606415",
		"referer_ip":  "10.0.0.1",
		"etag":        "\"afZYj912P4alikMz_P11982\"",
		"request_id":  "86400",
		"untrimmed":   "  untrimmed  ",
	}
	values := url.Values{}
	for key, value := range kvmap {
		values.Set(key, value)
	}

	tok := Token{Raw: values}

	f := func(key string, want interface{}) {
		t.Helper()

		value := tok.Extra(key)
		if value != want {
			t.Errorf("got %q; want %q", value, want)
		}
	}

	f("scope", "user")
	f("server_time", 1443571905.5606415)
	f("referer_ip", "10.0.0.1")
	f("expires_in", 86400.92)
	f("request_id", int64(86400))
	f("untrimmed", "  untrimmed  ")
}


<|start_filename|>oauth2_test.go<|end_filename|>
package oauth2

import (
	"net/http"
	"net/url"
	"testing"
)

func TestAuthCodeURL(t *testing.T) {
	f := func(cfg Config, state string, want string) {
		client := NewClient(http.DefaultClient, cfg)

		url := client.AuthCodeURL(state)
		if url != want {
			t.Errorf("got %q; want %q", url, want)
		}
	}

	f(
		Config{
			ClientID:     "CLIENT_ID",
			ClientSecret: "CLIENT_SECRET",
			RedirectURL:  "REDIRECT_URL",
			Scopes:       nil,
			AuthURL:      "server:1234/auth",
			TokenURL:     "",
		},
		"test-state",
		`server:1234/auth?client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&state=test-state`,
	)

	f(
		Config{
			ClientID:     "CLIENT_ID",
			ClientSecret: "CLIENT_SECRET",
			RedirectURL:  "REDIRECT_URL",
			Scopes:       nil,
			AuthURL:      "server:1234/auth?foo=bar",
			TokenURL:     "",
		},
		"test-state",
		`server:1234/auth?foo=bar&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&state=test-state`,
	)

	f(
		Config{
			ClientID:     "CLIENT_ID",
			ClientSecret: "CLIENT_SECRET",
			RedirectURL:  "REDIRECT_URL",
			Scopes:       []string{"scope1", "scope2"},
			AuthURL:      "server:1234/auth",
			TokenURL:     "",
		},
		"test-state",
		`server:1234/auth?client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=test-state`,
	)

	f(
		Config{
			ClientID: "CLIENT_ID",
			AuthURL:  "server:1234/auth-url",
			TokenURL: "",
		},
		"",
		`server:1234/auth-url?client_id=CLIENT_ID&response_type=code`,
	)
}

func TestAuthCodeURLWithParams(t *testing.T) {
	f := func(cfg Config, state string, params url.Values, want string) {
		client := NewClient(http.DefaultClient, cfg)

		url := client.AuthCodeURLWithParams(state, params)
		if url != want {
			t.Errorf("got %q; want %q", url, want)
		}
	}

	f(
		Config{
			ClientID:     "CLIENT_ID",
			ClientSecret: "CLIENT_SECRET",
			RedirectURL:  "REDIRECT_URL",
			Scopes:       nil,
			AuthURL:      "server:1234/auth",
			TokenURL:     "",
		},
		"test-state",
		nil,
		`server:1234/auth?client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&state=test-state`,
	)

	f(
		Config{
			ClientID:     "CLIENT_ID",
			ClientSecret: "CLIENT_SECRET",
			RedirectURL:  "REDIRECT_URL",
			Scopes:       nil,
			AuthURL:      "server:1234/auth?foo=bar",
			TokenURL:     "",
		},
		"test-state",
		nil,
		`server:1234/auth?foo=bar&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&state=test-state`,
	)

	f(
		Config{
			ClientID:     "CLIENT_ID",
			ClientSecret: "CLIENT_SECRET",
			RedirectURL:  "REDIRECT_URL",
			Scopes:       []string{"scope1", "scope2"},
			AuthURL:      "server:1234/auth",
			TokenURL:     "",
		},
		"test-state",
		url.Values{
			"access_type": []string{"anything"},
			"param1":      []string{"value1"},
		},
		`server:1234/auth?access_type=anything&client_id=CLIENT_ID&param1=value1&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=test-state`,
	)

	f(
		Config{
			ClientID: "CLIENT_ID",
			AuthURL:  "server:1234/auth-url",
			TokenURL: "",
		},
		"",
		nil,
		`server:1234/auth-url?client_id=CLIENT_ID&response_type=code`,
	)
}


<|start_filename|>client.go<|end_filename|>
package oauth2

import (
	"bytes"
	"context"
	"errors"
	"net/http"
	"net/url"
	"strings"
)

// Client represents an OAuth2 HTTP client.
//
type Client struct {
	client *http.Client
	config Config
}

// NewClient instantiates a new client with a given config.
//
func NewClient(client *http.Client, config Config) *Client {
	c := &Client{
		client: client,
		config: config,
	}
	return c
}

// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
// that asks for permissions for the required scopes explicitly.
//
// State is a token to protect the user from CSRF attacks.
//
// You must always provide a non-empty string and validate that it matches the
// the state query parameter on your redirect callback.
//
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
//
func (c *Client) AuthCodeURL(state string) string {
	return c.AuthCodeURLWithParams(state, nil)
}

// AuthCodeURLWithParams same as AuthCodeURL but allows to pass additional URL parameters.
//
func (c *Client) AuthCodeURLWithParams(state string, vals url.Values) string {
	// TODO(cristaloleg): can be set once (except state).
	v := cloneURLValues(vals)
	v.Add("response_type", "code")
	v.Add("client_id", c.config.ClientID)

	if c.config.RedirectURL != "" {
		v.Set("redirect_uri", c.config.RedirectURL)
	}
	if len(c.config.Scopes) > 0 {
		v.Set("scope", strings.Join(c.config.Scopes, " "))
	}
	if state != "" {
		v.Set("state", state)
	}

	var buf bytes.Buffer
	buf.WriteString(c.config.AuthURL)

	if strings.Contains(c.config.AuthURL, "?") {
		buf.WriteByte('&')
	} else {
		buf.WriteByte('?')
	}

	buf.WriteString(v.Encode())
	return buf.String()
}

// Exchange converts an authorization code into an OAuth2 token.
//
func (c *Client) Exchange(ctx context.Context, code string) (*Token, error) {
	return c.ExchangeWithParams(ctx, code, nil)
}

// ExchangeWithParams converts an authorization code into an OAuth2 token.
//
func (c *Client) ExchangeWithParams(ctx context.Context, code string, params url.Values) (*Token, error) {
	vals := cloneURLValues(params)
	vals.Add("grant_type", "authorization_code")
	vals.Add("code", code)

	if c.config.RedirectURL != "" {
		vals.Set("redirect_uri", c.config.RedirectURL)
	}
	return c.retrieveToken(ctx, vals)
}

// CredentialsToken retrieves a token for given username and password.
//
func (c *Client) CredentialsToken(ctx context.Context, username, password string) (*Token, error) {
	vals := url.Values{
		"grant_type": []string{"password"},
		"username":   []string{username},
		"password":   []string{password},
	}

	if len(c.config.Scopes) > 0 {
		vals.Set("scope", strings.Join(c.config.Scopes, " "))
	}
	return c.retrieveToken(ctx, vals)
}

// Token renews a token based on previous token.
//
func (c *Client) Token(ctx context.Context, refreshToken string) (*Token, error) {
	if refreshToken == "" {
		return nil, errors.New("oauth2: refresh token is not set")
	}

	vals := url.Values{
		"grant_type":    []string{"refresh_token"},
		"refresh_token": []string{refreshToken},
	}

	return c.retrieveToken(ctx, vals)
}

func (c *Client) retrieveToken(ctx context.Context, vals url.Values) (*Token, error) {
	mode := c.config.Mode

	shouldGuessAuthMode := mode == AutoDetectMode
	if shouldGuessAuthMode {
		mode = InHeaderMode
	}

	token, err := c.makeRequest(ctx, mode, vals)
	if err == nil {
		return token, nil
	}
	if !shouldGuessAuthMode {
		return nil, err
	}
	mode = InParamsMode

	token, err = c.makeRequest(ctx, mode, vals)
	if err != nil {
		return nil, err
	}
	c.config.Mode = mode
	return token, nil
}

func (c *Client) makeRequest(ctx context.Context, mode Mode, vals url.Values) (*Token, error) {
	req, err := c.newTokenRequest(mode, vals)
	if err != nil {
		return nil, err
	}
	resp, err := c.client.Do(req.WithContext(ctx))
	if err != nil {
		return nil, err
	}
	token, err := parseResponse(resp)
	if err != nil {
		return nil, err
	}
	return token, nil
}

func (c *Client) newTokenRequest(mode Mode, v url.Values) (*http.Request, error) {
	clientID, clientSecret := c.config.ClientID, c.config.ClientSecret

	if mode == InParamsMode {
		v = cloneURLValues(v)
		if clientID != "" {
			v.Set("client_id", clientID)
		}
		if clientSecret != "" {
			v.Set("client_secret", clientSecret)
		}
	}

	req, err := http.NewRequest("POST", c.config.TokenURL, strings.NewReader(v.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	if mode == InHeaderMode {
		req.SetBasicAuth(url.QueryEscape(clientID), url.QueryEscape(clientSecret))
	}
	return req, nil
}


<|start_filename|>example_test.go<|end_filename|>
package oauth2_test

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/cristalhq/oauth2"
)

func ExampleClientAndConfig() {
	config := oauth2.Config{
		ClientID:     "YOUR_CLIENT_ID",
		ClientSecret: "YOUR_CLIENT_SECRET",
		AuthURL:      "https://provider.com/o/oauth2/auth",
		TokenURL:     "https://provider.com/o/oauth2/token",
		Scopes:       []string{"email", "avatar"},
	}

	// create a client
	client := oauth2.NewClient(http.DefaultClient, config)

	// url to fetch the code
	url := client.AuthCodeURL("state")
	fmt.Printf("Visit the URL for the auth dialog: %v", url)

	// Use the authorization code that is pushed to the redirect URL.
	// Exchange will do the handshake to retrieve the initial access token.
	var code string
	if _, err := fmt.Scan(&code); err != nil {
		log.Fatal(err)
	}

	// get a token
	token, err := client.Exchange(context.Background(), code)
	if err != nil {
		panic(err)
	}

	var _ string = token.AccessToken  // OAuth2 token
	var _ string = token.TokenType    // type of the token
	var _ string = token.RefreshToken // token for a refresh
	var _ time.Time = token.Expiry    // token expiration time
	var _ bool = token.IsExpired()    // have token expired?
}


<|start_filename|>oauth2.go<|end_filename|>
package oauth2

// Config describes a 3-legged OAuth2 flow.
type Config struct {
	ClientID     string   // ClientID is the application's ID.
	ClientSecret string   // ClientSecret is the application's secret.
	AuthURL      string   // AuthURL is a URL for authentication.
	TokenURL     string   // TokenURL is a URL for retrieving a token.
	Mode         Mode     // Mode represents how tokens are represented in requests.
	RedirectURL  string   // RedirectURL is the URL to redirect users going through the OAuth flow.
	Scopes       []string // Scope specifies optional requested permissions.
}

// Mode represents how requests for tokens are authenticated to the server.
type Mode int

const (
	// AutoDetectMode means to auto-detect which authentication style the provider wants.
	AutoDetectMode Mode = 0

	// InParamsMode sends the `client_id` and `client_secret` in the POST body
	// as application/x-www-form-urlencoded parameters.
	InParamsMode Mode = 1

	// InHeaderMode sends the `client_id` and `client_secret` using HTTP Basic Authorization.
	// This is an optional style described in the OAuth2 RFC 6749 section 2.3.1.
	InHeaderMode Mode = 2
)


<|start_filename|>wrapper.go<|end_filename|>
package oauth2

import (
	"net/http"
)

// Wrap adds an additional header to the given http.Client.
// The header will be `Authorization`.
// All the params cannot be empty or nil.
//
func Wrap(header, value string, c *http.Client) (*http.Client, error) {
	transport := http.DefaultTransport
	if c.Transport != nil {
		transport = c.Transport
	}

	wrapped := &http.Client{
		Transport: &wrappedTransport{
			header:    header,
			value:     value,
			transport: transport,
		},
	}
	return wrapped, nil
}

type wrappedTransport struct {
	header    string
	value     string
	transport http.RoundTripper
}

// RoundTrip implements the http.RoundTripper interface.
func (t *wrappedTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	req = cloneRequest(req)
	req.Header.Set(t.header, t.value)
	return t.transport.RoundTrip(req)
}

func cloneRequest(r *http.Request) *http.Request {
	r2 := &http.Request{}
	*r2 = *r
	r2.Header = make(http.Header, len(r.Header))
	for k, s := range r.Header {
		r2.Header[k] = append([]string(nil), s...)
	}
	return r2
}


<|start_filename|>utils.go<|end_filename|>
package oauth2

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"mime"
	"net/http"
	"net/url"
	"strconv"
	"time"
)

func cloneURLValues(vals url.Values) url.Values {
	if vals == nil {
		return url.Values{}
	}

	v2 := make(url.Values, len(vals))
	for k, v := range vals {
		v2[k] = append([]string(nil), v...)
	}
	return v2
}

func parseResponse(resp *http.Response) (*Token, error) {
	body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
	resp.Body.Close()

	if err != nil {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
	}
	if resp.StatusCode < 200 || resp.StatusCode > 299 {
		return nil, fmt.Errorf("oauth2: cannot fetch token: %v %v\nResponse: %s",
			resp.StatusCode, http.StatusText(resp.StatusCode), string(body))
	}

	var token *Token

	switch responseContentType(resp) {
	case "text/plain", "application/x-www-form-urlencoded":
		token, err = parseText(body)
	default:
		token, err = parseJSON(body)
	}

	switch {
	case err != nil:
		return nil, err
	case token.AccessToken == "":
		return nil, errors.New("oauth2: server response missing access_token")
	default:
		return token, nil
	}
}

func responseContentType(resp *http.Response) string {
	content, _, _ := mime.ParseMediaType(resp.Header.Get("Content-Type"))
	return content
}

func parseText(body []byte) (*Token, error) {
	vals, err := url.ParseQuery(string(body))
	if err != nil {
		return nil, err
	}

	token := &Token{
		AccessToken:  vals.Get("access_token"),
		TokenType:    vals.Get("token_type"),
		RefreshToken: vals.Get("refresh_token"),
		Raw:          vals,
	}

	e := vals.Get("expires_in")
	expires, _ := strconv.Atoi(e)
	if expires != 0 {
		token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
	}
	return token, nil
}

func parseJSON(body []byte) (*Token, error) {
	var tj tokenJSON
	if err := json.Unmarshal(body, &tj); err != nil {
		return nil, err
	}

	token := &Token{
		AccessToken:  tj.AccessToken,
		TokenType:    tj.TokenType,
		RefreshToken: tj.RefreshToken,
		Expiry:       tj.expiry(),
		Raw:          make(map[string]interface{}),
	}

	_ = json.Unmarshal(body, &token.Raw) // no error checks for optional fields

	return token, nil
}

// tokenJSON represens the HTTP response from OAuth2 providers.
type tokenJSON struct {
	AccessToken  string         `json:"access_token"`
	TokenType    string         `json:"token_type"`
	RefreshToken string         `json:"refresh_token"`
	ExpiresIn    expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
}

func (e *tokenJSON) expiry() (t time.Time) {
	if v := e.ExpiresIn; v != 0 {
		return time.Now().Add(time.Duration(v) * time.Second)
	}
	return
}

type expirationTime int32

func (e *expirationTime) UnmarshalJSON(b []byte) error {
	if len(b) == 0 || string(b) == "null" {
		return nil
	}
	var n json.Number
	err := json.Unmarshal(b, &n)
	if err != nil {
		return err
	}
	i, err := n.Int64()
	if err != nil {
		return err
	}
	if i > math.MaxInt32 {
		i = math.MaxInt32
	}
	*e = expirationTime(i)
	return nil
}
