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

Google - continually use refresh token #117

Merged
merged 1 commit into from
Jul 3, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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