Skip to content
This repository has been archived by the owner on Jan 24, 2019. It is now read-only.

Commit

Permalink
Merge pull request #117 from jehiah/always_refresh_117
Browse files Browse the repository at this point in the history
Google - continually use refresh token
  • Loading branch information
jehiah committed Jul 3, 2015
2 parents b9ae5dc + d49c3e1 commit 01c9d04
Show file tree
Hide file tree
Showing 21 changed files with 866 additions and 580 deletions.
3 changes: 3 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package api
import (
"fmt"
"io/ioutil"
"log"
"net/http"

"github.com/bitly/go-simplejson"
Expand All @@ -11,10 +12,12 @@ import (
func Request(req *http.Request) (*simplejson.Json, error) {
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("%s %s %s", req.Method, req.URL, err)
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
log.Printf("%d %s %s %s", resp.StatusCode, req.Method, req.URL, body)
if err != nil {
return nil, err
}
Expand Down
128 changes: 128 additions & 0 deletions cookie/cookies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package cookie

import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)

// cookies are stored in a 3 part (value + timestamp + signature) to enforce that the values are as originally set.
// additionally, the 'value' is encrypted so it's opaque to the browser

// Validate ensures a cookie is properly signed
func Validate(cookie *http.Cookie, seed string, expiration time.Duration) (value string, t time.Time, ok bool) {
// value, timestamp, sig
parts := strings.Split(cookie.Value, "|")
if len(parts) != 3 {
return
}
sig := cookieSignature(seed, cookie.Name, parts[0], parts[1])
if checkHmac(parts[2], sig) {
ts, err := strconv.Atoi(parts[1])
if err != nil {
return
}
// The expiration timestamp set when the cookie was created
// isn't sent back by the browser. Hence, we check whether the
// creation timestamp stored in the cookie falls within the
// window defined by (Now()-expiration, Now()].
t = time.Unix(int64(ts), 0)
if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) {
// it's a valid cookie. now get the contents
rawValue, err := base64.URLEncoding.DecodeString(parts[0])
if err == nil {
value = string(rawValue)
ok = true
return
}
}
}
return
}

// SignedValue returns a cookie that is signed and can later be checked with Validate
func SignedValue(seed string, key string, value string, now time.Time) string {
encodedValue := base64.URLEncoding.EncodeToString([]byte(value))
timeStr := fmt.Sprintf("%d", now.Unix())
sig := cookieSignature(seed, key, encodedValue, timeStr)
cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig)
return cookieVal
}

func cookieSignature(args ...string) string {
h := hmac.New(sha1.New, []byte(args[0]))
for _, arg := range args[1:] {
h.Write([]byte(arg))
}
var b []byte
b = h.Sum(b)
return base64.URLEncoding.EncodeToString(b)
}

func checkHmac(input, expected string) bool {
inputMAC, err1 := base64.URLEncoding.DecodeString(input)
if err1 == nil {
expectedMAC, err2 := base64.URLEncoding.DecodeString(expected)
if err2 == nil {
return hmac.Equal(inputMAC, expectedMAC)
}
}
return false
}

// Cipher provides methods to encrypt and decrypt cookie values
type Cipher struct {
cipher.Block
}

// NewCipher returns a new aes Cipher for encrypting cookie values
func NewCipher(secret string) (*Cipher, error) {
c, err := aes.NewCipher([]byte(secret))
if err != nil {
return nil, err
}
return &Cipher{Block: c}, err
}

// Encrypt a value for use in a cookie
func (c *Cipher) Encrypt(value string) (string, error) {
ciphertext := make([]byte, aes.BlockSize+len(value))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return "", fmt.Errorf("failed to create initialization vector %s", err)
}

stream := cipher.NewCFBEncrypter(c.Block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(value))
return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// Decrypt a value from a cookie to it's original string
func (c *Cipher) Decrypt(s string) (string, error) {
encrypted, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", fmt.Errorf("failed to decrypt cookie value %s", err)
}

if len(encrypted) < aes.BlockSize {
return "", fmt.Errorf("encrypted cookie value should be "+
"at least %d bytes, but is only %d bytes",
aes.BlockSize, len(encrypted))
}

iv := encrypted[:aes.BlockSize]
encrypted = encrypted[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(c.Block, iv)
stream.XORKeyStream(encrypted, encrypted)

return string(encrypted), nil
}
23 changes: 23 additions & 0 deletions cookie/cookies_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cookie

import (
"testing"

"github.com/bmizerany/assert"
)

func TestEncodeAndDecodeAccessToken(t *testing.T) {
const secret = "0123456789abcdefghijklmnopqrstuv"
const token = "my access token"
c, err := NewCipher(secret)
assert.Equal(t, nil, err)

encoded, err := c.Encrypt(token)
assert.Equal(t, nil, err)

decoded, err := c.Decrypt(encoded)
assert.Equal(t, nil, err)

assert.NotEqual(t, token, encoded)
assert.Equal(t, token, decoded)
}
140 changes: 0 additions & 140 deletions cookies.go

This file was deleted.

75 changes: 0 additions & 75 deletions cookies_test.go

This file was deleted.

Loading

0 comments on commit 01c9d04

Please sign in to comment.