Packages app and gfit: Unify HTTP retries with retry.Transport.
[kraftakt.git] / app / user.go
index 1215653..63cd086 100644 (file)
@@ -2,11 +2,15 @@ package app
 
 import (
        "context"
+       "crypto/hmac"
+       "crypto/sha1"
+       "encoding/hex"
        "fmt"
        "net/http"
        "sync"
 
        "github.com/google/uuid"
+       "github.com/octo/retry"
        legacy_context "golang.org/x/net/context"
        "golang.org/x/oauth2"
        "google.golang.org/appengine/datastore"
@@ -14,8 +18,10 @@ import (
 )
 
 type User struct {
+       key *datastore.Key
+
+       ID    string
        Email string
-       key   *datastore.Key
 }
 
 type dbUser struct {
@@ -23,14 +29,23 @@ type dbUser struct {
 }
 
 func NewUser(ctx context.Context, email string) (*User, error) {
+       var id string
        err := datastore.RunInTransaction(ctx, func(ctx legacy_context.Context) error {
                key := datastore.NewKey(ctx, "User", email, 0, nil)
-               if err := datastore.Get(ctx, key, &dbUser{}); err != datastore.ErrNoSuchEntity {
-                       return err // may be nil
+
+               var u dbUser
+               err := datastore.Get(ctx, key, &u)
+               if err != nil && err != datastore.ErrNoSuchEntity {
+                       return err
+               }
+               if err == nil {
+                       id = u.ID
+                       return nil
                }
 
-               _, err := datastore.Put(ctx, key, &dbUser{
-                       ID: uuid.New().String(),
+               id = uuid.New().String()
+               _, err = datastore.Put(ctx, key, &dbUser{
+                       ID: id,
                })
                return err
        }, nil)
@@ -39,8 +54,9 @@ func NewUser(ctx context.Context, email string) (*User, error) {
        }
 
        return &User{
-               Email: email,
                key:   datastore.NewKey(ctx, "User", email, 0, nil),
+               ID:    id,
+               Email: email,
        }, nil
 }
 
@@ -55,20 +71,12 @@ func UserByID(ctx context.Context, id string) (*User, error) {
        }
 
        return &User{
-               Email: keys[0].StringID(),
                key:   keys[0],
+               ID:    id,
+               Email: keys[0].StringID(),
        }, nil
 }
 
-func (u *User) ID(ctx context.Context) (string, error) {
-       var db dbUser
-       if err := datastore.Get(ctx, u.key, &db); err != nil {
-               return "", err
-       }
-
-       return db.ID, nil
-}
-
 func (u *User) Token(ctx context.Context, svc string) (*oauth2.Token, error) {
        key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
 
@@ -86,21 +94,42 @@ func (u *User) SetToken(ctx context.Context, svc string, tok *oauth2.Token) erro
        return err
 }
 
+func (u *User) DeleteToken(ctx context.Context, svc string) error {
+       key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
+       return datastore.Delete(ctx, key)
+}
+
 func (u *User) OAuthClient(ctx context.Context, svc string, cfg *oauth2.Config) (*http.Client, error) {
        key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
 
        var tok oauth2.Token
        if err := datastore.Get(ctx, key, &tok); err != nil {
-               return nil, err
+               return nil, fmt.Errorf("datastore.Get(%v) = %v", key, err)
        }
 
        src := cfg.TokenSource(ctx, &tok)
-       return oauth2.NewClient(ctx, &persistingTokenSource{
+       c := oauth2.NewClient(ctx, &persistingTokenSource{
                ctx: ctx,
                t:   &tok,
                src: src,
                key: key,
-       }), nil
+       })
+       c.Transport = retry.Transport{
+               RoundTripper: c.Transport,
+       }
+
+       return c, nil
+}
+
+func (u *User) String() string {
+       return u.Email
+}
+
+func (u *User) Sign(payload string) string {
+       mac := hmac.New(sha1.New, []byte(u.ID))
+       mac.Write([]byte(payload))
+
+       return hex.EncodeToString(mac.Sum(nil))
 }
 
 type persistingTokenSource struct {
@@ -121,7 +150,10 @@ func (s *persistingTokenSource) Token() (*oauth2.Token, error) {
                return nil, err
        }
 
-       if s.t.RefreshToken != tok.RefreshToken {
+       if s.t.AccessToken != tok.AccessToken ||
+               s.t.TokenType != tok.TokenType ||
+               s.t.RefreshToken != tok.RefreshToken ||
+               !s.t.Expiry.Equal(tok.Expiry) {
                if _, err := datastore.Put(s.ctx, s.key, tok); err != nil {
                        log.Errorf(s.ctx, "persisting OAuth token in datastore failed: %v", err)
                }