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"
+ "google.golang.org/appengine/log"
)
type User struct {
key *datastore.Key
+
+ ID string
+ Email string
}
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)
}
return &User{
- key: datastore.NewKey(ctx, "User", email, 0, nil),
+ key: datastore.NewKey(ctx, "User", email, 0, nil),
+ ID: id,
+ Email: email,
}, nil
}
}
return &User{
- key: keys[0],
+ 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)
}
func (u *User) SetToken(ctx context.Context, svc string, tok *oauth2.Token) error {
- key := datastore.NewKey(ctx, "Token", "Fitbit", 0, u.key)
+ key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
_, err := datastore.Put(ctx, key, tok)
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, fmt.Errorf("datastore.Get(%v) = %v", key, err)
+ }
+
+ src := cfg.TokenSource(ctx, &tok)
+ c := oauth2.NewClient(ctx, &persistingTokenSource{
+ ctx: ctx,
+ t: &tok,
+ src: src,
+ key: key,
+ })
+ 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 {
+ ctx context.Context
+ t *oauth2.Token
+ src oauth2.TokenSource
+ key *datastore.Key
+
+ sync.Mutex
+}
+
+func (s *persistingTokenSource) Token() (*oauth2.Token, error) {
+ s.Lock()
+ defer s.Unlock()
+
+ tok, err := s.src.Token()
+ if err != nil {
+ return nil, err
+ }
+
+ 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)
+ }
+ }
+
+ s.t = tok
+ return tok, nil
+}