Package app: Implement User.DeleteToken() and User.String()
[kraftakt.git] / app / user.go
1 package app
2
3 import (
4         "context"
5         "fmt"
6         "net/http"
7         "sync"
8
9         "github.com/google/uuid"
10         legacy_context "golang.org/x/net/context"
11         "golang.org/x/oauth2"
12         "google.golang.org/appengine/datastore"
13         "google.golang.org/appengine/log"
14 )
15
16 type User struct {
17         Email string
18         key   *datastore.Key
19 }
20
21 type dbUser struct {
22         ID string
23 }
24
25 func NewUser(ctx context.Context, email string) (*User, error) {
26         err := datastore.RunInTransaction(ctx, func(ctx legacy_context.Context) error {
27                 key := datastore.NewKey(ctx, "User", email, 0, nil)
28                 if err := datastore.Get(ctx, key, &dbUser{}); err != datastore.ErrNoSuchEntity {
29                         return err // may be nil
30                 }
31
32                 _, err := datastore.Put(ctx, key, &dbUser{
33                         ID: uuid.New().String(),
34                 })
35                 return err
36         }, nil)
37         if err != nil {
38                 return nil, err
39         }
40
41         return &User{
42                 Email: email,
43                 key:   datastore.NewKey(ctx, "User", email, 0, nil),
44         }, nil
45 }
46
47 func UserByID(ctx context.Context, id string) (*User, error) {
48         q := datastore.NewQuery("User").Filter("ID=", id).KeysOnly()
49         keys, err := q.GetAll(ctx, nil)
50         if err != nil {
51                 return nil, fmt.Errorf("datastore.Query.GetAll(): %v", err)
52         }
53         if len(keys) != 1 {
54                 return nil, fmt.Errorf("len(keys) = %d, want 1", len(keys))
55         }
56
57         return &User{
58                 Email: keys[0].StringID(),
59                 key:   keys[0],
60         }, nil
61 }
62
63 func (u *User) ID(ctx context.Context) (string, error) {
64         var db dbUser
65         if err := datastore.Get(ctx, u.key, &db); err != nil {
66                 return "", err
67         }
68
69         return db.ID, nil
70 }
71
72 func (u *User) Token(ctx context.Context, svc string) (*oauth2.Token, error) {
73         key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
74
75         var tok oauth2.Token
76         if err := datastore.Get(ctx, key, &tok); err != nil {
77                 return nil, err
78         }
79
80         return &tok, nil
81 }
82
83 func (u *User) SetToken(ctx context.Context, svc string, tok *oauth2.Token) error {
84         key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
85         _, err := datastore.Put(ctx, key, tok)
86         return err
87 }
88
89 func (u *User) DeleteToken(ctx context.Context, svc string) error {
90         key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
91         return datastore.Delete(ctx, key)
92 }
93
94 func (u *User) OAuthClient(ctx context.Context, svc string, cfg *oauth2.Config) (*http.Client, error) {
95         key := datastore.NewKey(ctx, "Token", svc, 0, u.key)
96
97         var tok oauth2.Token
98         if err := datastore.Get(ctx, key, &tok); err != nil {
99                 return nil, err
100         }
101
102         src := cfg.TokenSource(ctx, &tok)
103         return oauth2.NewClient(ctx, &persistingTokenSource{
104                 ctx: ctx,
105                 t:   &tok,
106                 src: src,
107                 key: key,
108         }), nil
109 }
110
111 func (u *User) String() string {
112         return u.Email
113 }
114
115 type persistingTokenSource struct {
116         ctx context.Context
117         t   *oauth2.Token
118         src oauth2.TokenSource
119         key *datastore.Key
120
121         sync.Mutex
122 }
123
124 func (s *persistingTokenSource) Token() (*oauth2.Token, error) {
125         s.Lock()
126         defer s.Unlock()
127
128         tok, err := s.src.Token()
129         if err != nil {
130                 return nil, err
131         }
132
133         if s.t.AccessToken != tok.AccessToken ||
134                 s.t.TokenType != tok.TokenType ||
135                 s.t.RefreshToken != tok.RefreshToken ||
136                 !s.t.Expiry.Equal(tok.Expiry) {
137                 if _, err := datastore.Put(s.ctx, s.key, tok); err != nil {
138                         log.Errorf(s.ctx, "persisting OAuth token in datastore failed: %v", err)
139                 }
140         }
141
142         s.t = tok
143         return tok, nil
144 }